├── .github ├── dependabot.yaml └── workflows │ ├── codeql.yml │ └── go.yaml ├── .gitignore ├── .golangci.yaml ├── LICENSE ├── Makefile ├── README.md ├── acceptor ├── acceptor.go ├── constants.go ├── routes.go ├── webrtc_acceptor.go ├── webrtc_conn.go ├── webrtc_handler.go ├── webrtc_test.go ├── ws_acceptor.go ├── ws_conn.go ├── ws_handler.go └── ws_test.go ├── admin ├── admin.go └── status.go ├── agent ├── agent.go ├── constants.go └── factory.go ├── app.go ├── builder.go ├── codec ├── benchmark_test.go ├── codec.go ├── codec_test.go ├── constants.go ├── json_decode.go ├── json_encode.go ├── proto_decode.go ├── proto_encode.go ├── test.pb.go └── test.proto ├── codecov.yml ├── component └── component.go ├── config ├── config.go ├── config.yaml ├── config2.yaml └── config_test.go ├── deployment ├── deployment.go ├── deployment.tpl ├── deployment_test.go └── testdata │ ├── test1.yaml │ └── test2.yaml ├── dispatch ├── constants.go ├── dispatch.go └── method.go ├── docs └── .gitkeep ├── e2e ├── default.yaml ├── e2e_test.go ├── helper.go ├── performance │ └── main.go ├── smoke │ ├── config.yaml │ ├── data.proto │ ├── internal │ │ ├── logic │ │ │ └── baseworld │ │ │ │ └── bind_user_logic.go │ │ ├── message │ │ │ └── message.go │ │ ├── server │ │ │ └── server.go │ │ └── svc │ │ │ └── service_context.go │ ├── main.go │ └── model │ │ ├── data.ep.go │ │ └── data.pb.go └── testdata │ ├── fakeinternal │ ├── config │ │ └── config.go │ ├── logic │ │ ├── baseworld │ │ │ ├── bind_user_logic.go │ │ │ ├── join_world_logic.go │ │ │ └── update_user_in_world_logic.go │ │ └── web │ │ │ └── user_login_logic.go │ ├── message │ │ └── message.go │ ├── player │ │ ├── manager.go │ │ └── palyer.go │ ├── server │ │ ├── game_server.go │ │ └── web_server.go │ └── svc │ │ └── service_context.go │ ├── game.proto │ ├── game │ ├── game.ep.go │ └── game.pb.go │ ├── main.go │ └── run │ ├── config.yaml │ └── main.go ├── global └── global.go ├── go.mod ├── go.sum ├── group ├── constants.go ├── group.go ├── group_service.go ├── group_service_test.go ├── memory_group.go └── memory_group_test.go ├── gslog └── gslog.go ├── handler └── handler.go ├── latency └── latency.go ├── message └── message.go ├── networkentity └── networkentity.go ├── packet ├── benchmark_test.go ├── constants.go ├── packet.go ├── packet_test.go ├── pool.go ├── utils.go └── utils_test.go ├── proto ├── common.pb.go └── common.proto ├── session ├── constants.go ├── pool.go ├── session.go ├── session_data.go ├── session_memory.go └── session_memory_test.go ├── static.go ├── system.go ├── system └── system.ep.go ├── tools └── gogs │ ├── Makefile │ ├── cmd │ ├── csharp.go │ ├── docker.go │ ├── flags.go │ ├── init.go │ ├── proto.go │ └── root.go │ ├── csharp │ ├── gen.go │ ├── gen_test.go │ ├── gentemplate │ │ ├── Gogs │ │ │ ├── Codec.tpl │ │ │ ├── Common.tpl │ │ │ ├── EventsManager.tpl │ │ │ ├── ICodec.tpl │ │ │ ├── Messages.tpl │ │ │ └── Packet.tpl │ │ ├── Register.tpl │ │ └── template.go │ └── testdata │ │ ├── Model │ │ └── Register.cs │ │ ├── data.proto │ │ └── main.go │ ├── docker │ ├── gen.go │ └── gentemplate │ │ ├── docker.tpl │ │ └── template.go │ ├── gen │ ├── gen.go │ ├── gen_test.go │ ├── gentemplate │ │ ├── app.tpl │ │ ├── config.tpl │ │ ├── ep.go.tpl │ │ ├── go.mod.tpl │ │ ├── logic.tpl │ │ ├── message.tpl │ │ ├── proto.tpl │ │ ├── server.tpl │ │ ├── svc.tpl │ │ └── template.go │ ├── init.go │ ├── init_test.go │ └── testdata │ │ ├── config.yaml │ │ ├── data.proto │ │ ├── internal │ │ ├── logic │ │ │ └── baseworld │ │ │ │ └── bind_user_logic.go │ │ ├── message │ │ │ └── message.go │ │ ├── server │ │ │ └── server.go │ │ └── svc │ │ │ └── service_context.go │ │ ├── main.go │ │ └── model │ │ ├── data.ep.go │ │ └── data.pb.go │ ├── main.go │ ├── protoparse │ ├── comments.go │ └── parse.go │ └── setup.sh ├── unity ├── Protobuf │ ├── Google.Protobuf.dll │ ├── System.Buffers.dll │ ├── System.Memory.dll │ └── System.Runtime.CompilerServices.Unsafe.dll └── WebSocket │ ├── WebSocket.cs │ ├── WebSocket.jslib │ └── endel.nativewebsocket.asmdef ├── utils ├── bytebuffer │ ├── benchmark_test.go │ ├── bytebuffer.go │ ├── bytebufferpool.go │ └── bytebufferpool_test.go ├── execx │ ├── execx.go │ └── execx_test.go ├── filex │ ├── equal.go │ ├── equal_test.go │ └── testdata │ │ ├── a_go.mod │ │ ├── b_go.mod │ │ └── c_go.mod ├── gomod │ └── gomod.go ├── name │ ├── name.go │ └── name_test.go ├── randstr │ ├── randstr.go │ └── randstr_test.go ├── slicex │ ├── slicex.go │ └── slicex_test.go ├── snow │ ├── ip.go │ ├── ip_test.go │ ├── snow.go │ └── snow_test.go ├── stringx │ ├── stringx.go │ └── stringx_test.go └── templatex │ └── templatex.go ├── version ├── version.go └── webserver └── webserver.go /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | paths: 18 | - '**.go' 19 | - '**.tpl' 20 | pull_request: 21 | # The branches below must be a subset of the branches above 22 | branches: [ "main" ] 23 | paths: 24 | - '**.go' 25 | - '**.tpl' 26 | schedule: 27 | - cron: '31 18 * * 4' 28 | 29 | jobs: 30 | analyze: 31 | name: Analyze 32 | runs-on: ubuntu-latest 33 | permissions: 34 | actions: read 35 | contents: read 36 | security-events: write 37 | 38 | strategy: 39 | fail-fast: false 40 | matrix: 41 | language: [ 'go' ] 42 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 43 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 44 | 45 | steps: 46 | - name: Checkout repository 47 | uses: actions/checkout@v3 48 | 49 | # Initializes the CodeQL tools for scanning. 50 | - name: Initialize CodeQL 51 | uses: github/codeql-action/init@v2 52 | with: 53 | languages: ${{ matrix.language }} 54 | # If you wish to specify custom queries, you can do so here or in a config file. 55 | # By default, queries listed here will override any specified in a config file. 56 | # Prefix the list here with "+" to use these queries and those in the config file. 57 | 58 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 59 | # queries: security-extended,security-and-quality 60 | 61 | 62 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 63 | # If this step fails, then you should remove it and run the build manually (see below) 64 | - name: Autobuild 65 | uses: github/codeql-action/autobuild@v2 66 | 67 | # ℹ️ Command-line programs to run using the OS shell. 68 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 69 | 70 | # If the Autobuild fails above, remove it and uncomment the following three lines. 71 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 72 | 73 | # - run: | 74 | # echo "Run, Build Application using script" 75 | # ./location_of_script_within_repo/buildscript.sh 76 | 77 | - name: Perform CodeQL Analysis 78 | uses: github/codeql-action/analyze@v2 79 | -------------------------------------------------------------------------------- /.github/workflows/go.yaml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths: 7 | - "**.go" 8 | - "**.tpl" 9 | pull_request: 10 | branches: [main] 11 | paths: 12 | - "**.go" 13 | - "**.tpl" 14 | 15 | jobs: 16 | test-linux: 17 | name: Linux 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Set up Go 1.21 21 | uses: actions/setup-go@v2 22 | with: 23 | go-version: 1.21 24 | id: go 25 | 26 | - name: Check out code into the Go module directory 27 | uses: actions/checkout@v3 28 | 29 | - name: Get dependencies 30 | run: | 31 | go get -v -t -d ./... 32 | 33 | - name: Test 34 | run: go test -cover -coverpkg=./... -coverprofile coverage.out -covermode=count ./... --count=1 35 | 36 | - name: Codecov 37 | uses: codecov/codecov-action@v2 38 | 39 | - name: Convert cover report 40 | run: | 41 | go tool cover -o coverage.html -html=coverage.out 42 | 43 | - name: Upload a Build Artifact 44 | uses: actions/upload-artifact@v3.0.0 45 | with: 46 | # Artifact name 47 | name: coverage.html # optional, default is artifact 48 | # A file, directory or wildcard pattern that describes what to upload 49 | path: coverage.html 50 | retention-days: 5 51 | 52 | golangci: 53 | name: lint 54 | runs-on: macos-latest 55 | steps: 56 | - uses: actions/checkout@v2 57 | - name: Setup Go environment 58 | uses: actions/setup-go@v3.0.0 59 | with: 60 | # The Go version to download (if necessary) and use. Supports semver spec and ranges. 61 | go-version: 1.21 # optional 62 | 63 | - name: golangci-lint 64 | uses: golangci/golangci-lint-action@v3 65 | with: 66 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 67 | version: latest 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.out 2 | mem.pprof 3 | cpu.pprof 4 | trace.out -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | run: 2 | go: "1.21" 3 | timeout: 30m 4 | skip-dirs: 5 | - .cache 6 | skip-files: 7 | - ".*pb\\.go" 8 | tests: false 9 | 10 | issues: 11 | max-same-issues: 200 12 | 13 | linters: 14 | disable-all: true 15 | enable: 16 | # 必须 17 | - errcheck 18 | - govet 19 | - ineffassign 20 | - staticcheck 21 | - bodyclose 22 | - durationcheck 23 | - exportloopref 24 | - gomodguard 25 | - gosec 26 | 27 | # 建议 28 | - gosimple 29 | - decorder 30 | - dogsled 31 | - errchkjson 32 | - errorlint 33 | - goprintffuncname 34 | - makezero 35 | - misspell 36 | - nakedret 37 | - nestif 38 | - nilerr # 后期建议放入,所有err均需要有处理 39 | - nilnil 40 | - noctx 41 | - unconvert 42 | linters-settings: 43 | gosec: 44 | excludes: 45 | - G401 46 | - G505 47 | - G501 48 | - G402 49 | - G204 50 | - G101 51 | - G107 52 | - G114 53 | 54 | gosimple: 55 | go: "1.21" 56 | checks: ["all", "-SA1019", "-S1039", "-S1025", "-S1008"] 57 | staticcheck: 58 | go: "1.21" 59 | checks: ["all", "-SA5008", "-SA9003", "-SA1029", "-SA4006"] 60 | errcheck: 61 | check-type-assertions: true 62 | check-blank: false 63 | revie: 64 | enable-all-rules: true 65 | ignore-generated-header: true 66 | rules: 67 | - name: var-naming 68 | disabled: true 69 | govet: 70 | disable-all: true 71 | enable: 72 | - asmdecl 73 | - assign 74 | - atomic 75 | - atomicalign 76 | - bools 77 | - buildtag 78 | - cgocall 79 | - composites 80 | - copylocks 81 | - deepequalerrors 82 | - errorsas 83 | - findcall 84 | - framepointer 85 | - httpresponse 86 | - ifaceassert 87 | - loopclosure 88 | - lostcancel 89 | - nilfunc 90 | - nilness 91 | - printf 92 | - reflectvaluecompare 93 | # - shadow 94 | - shift 95 | - sigchanyzer 96 | - sortslice 97 | - stdmethods 98 | - stringintconv 99 | - structtag 100 | - testinggoroutine 101 | - tests 102 | - unmarshal 103 | - unreachable 104 | - unsafeptr 105 | - unusedresult 106 | # - unusedwrite 107 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: vet 2 | vet: 3 | @go vet $(go list ./...) 4 | 5 | .PHONY: lint 6 | lint: 7 | @golangci-lint run 8 | 9 | .PHONY: test 10 | test: 11 | @go test -cover -coverpkg=./... -coverprofile coverage.out -covermode=atomic ./... --count=1 12 | @cat coverage.out | grep -v "testdata/" | grep -v "pb.go" | grep -v "examples" > coverage.tmp 13 | @mv coverage.tmp coverage.out 14 | 15 | .PHONY: testview 16 | testview: 17 | @go tool cover -html=coverage.out 18 | 19 | .PHONY: testcover 20 | testcover: 21 | @go tool cover -func=coverage.out | tail -n 1 -------------------------------------------------------------------------------- /acceptor/acceptor.go: -------------------------------------------------------------------------------- 1 | package acceptor 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | type ConnInfo struct { 9 | AcceptorType string // the type of the acceptor 10 | AcceptorName string // the name of the acceptor 11 | AcceptorGroup string // the group of the acceptor 12 | Ordered bool // whether the message is ordered, only for webrtc 13 | BucketFillInterval time.Duration // the interval of the bucket fill 14 | BucketCapacity int64 // the capacity of the bucket 15 | } 16 | 17 | // AcceptorConn interface 18 | type AcceptorConn interface { 19 | GetNextMessage() (b []byte, err error) // get the next message 20 | GetInfo() *ConnInfo // get the conn info 21 | SetCloseHandler(func()) // set the close callback 22 | IsClosed() bool // is conn closed 23 | net.Conn // Conn 24 | } 25 | 26 | // Acceptor type interface 27 | type Acceptor interface { 28 | GetConfig() *AcceptorConfig // get the config 29 | ListenAndServe() // listen and serve 30 | Stop() // stop the acceptor 31 | GetConnChan() chan AcceptorConn // get the conn channel 32 | GetAddr() string // get the addr 33 | GetName() string // get the name 34 | GetType() string // get the type 35 | } 36 | -------------------------------------------------------------------------------- /acceptor/constants.go: -------------------------------------------------------------------------------- 1 | package acceptor 2 | 3 | import "errors" 4 | 5 | var ( 6 | ACCEPTOR_TYPE_WS = "websockets" 7 | ACCEPTOR_TYPE_WEBRTC = "webrtc" 8 | 9 | ErrMessageRateLimit = errors.New("message rate limit") 10 | ) 11 | 12 | const ( 13 | _ int32 = iota 14 | ConnStatusStart 15 | ConnStatusClosed 16 | ) 17 | 18 | const ( 19 | _ int32 = iota 20 | StatusWorking 21 | StatusClosed 22 | ) 23 | -------------------------------------------------------------------------------- /acceptor/routes.go: -------------------------------------------------------------------------------- 1 | package acceptor 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | ) 7 | 8 | type MiddlewareFunc func(http.Handler) http.Handler 9 | 10 | type AcceptorConfig struct { 11 | Name string // the name of the acceptor 12 | Groups []*AcceptorGroupConfig // the groups of the acceptor 13 | Type string // the type of the acceptor 14 | HttpPort int 15 | UdpPort int 16 | } 17 | 18 | type AcceptorGroupConfig struct { 19 | GroupName string // the name of the group 20 | BucketFillInterval time.Duration // the interval of the bucket fill 21 | BucketCapacity int64 // the capacity of the bucket 22 | Ordered bool // whether the message is ordered, only for webrtc 23 | MiddlewareFunc []MiddlewareFunc 24 | } 25 | -------------------------------------------------------------------------------- /acceptor/webrtc_acceptor.go: -------------------------------------------------------------------------------- 1 | package acceptor 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "os" 8 | "sync" 9 | "sync/atomic" 10 | 11 | "github.com/metagogs/gogs/gslog" 12 | "github.com/pion/webrtc/v3" 13 | "go.uber.org/zap" 14 | ) 15 | 16 | // WebRTCAcceptor accept the webrtc data channel connection 17 | type WebRTCAcceptor struct { 18 | connChan chan AcceptorConn 19 | httpListener net.Listener 20 | udpListener *net.UDPConn 21 | api *webrtc.API 22 | config *AcceptorConfig 23 | state int32 24 | closeMutex sync.Mutex 25 | } 26 | 27 | func NewWebRTCAcceptor(config *AcceptorConfig) *WebRTCAcceptor { 28 | if len(config.Name) == 0 { 29 | gslog.NewLog("webrtc_acceptor").Error("name length is 0") 30 | os.Exit(1) 31 | } 32 | if len(config.Groups) == 0 { 33 | gslog.NewLog("webrtc_acceptor").Error("groups length is 0") 34 | os.Exit(1) 35 | } 36 | return &WebRTCAcceptor{ 37 | config: config, 38 | connChan: make(chan AcceptorConn), 39 | } 40 | } 41 | 42 | func (w *WebRTCAcceptor) GetConfig() *AcceptorConfig { 43 | return w.config 44 | } 45 | 46 | func (w *WebRTCAcceptor) GetName() string { 47 | return w.config.Name 48 | } 49 | 50 | func (w *WebRTCAcceptor) GetConnChan() chan AcceptorConn { 51 | return w.connChan 52 | } 53 | 54 | func (w *WebRTCAcceptor) GetAddr() string { 55 | return fmt.Sprintf("%d[udp-webrtc] %d[http]", w.config.UdpPort, w.config.HttpPort) 56 | } 57 | 58 | func (w *WebRTCAcceptor) GetType() string { 59 | return ACCEPTOR_TYPE_WEBRTC 60 | } 61 | 62 | func (w *WebRTCAcceptor) Stop() { 63 | w.closeMutex.Lock() 64 | defer w.closeMutex.Unlock() 65 | 66 | if atomic.LoadInt32(&w.state) == StatusClosed { 67 | return 68 | } 69 | errUdp := w.udpListener.Close() 70 | if errUdp != nil { 71 | gslog.NewLog("webrtc_acceptor").Error("Failed to stop udp", zap.Error(errUdp)) 72 | } 73 | errHttp := w.httpListener.Close() 74 | if errHttp != nil { 75 | gslog.NewLog("webrtc_acceptor").Error("Failed to stop http", zap.Error(errHttp)) 76 | } 77 | if errHttp == nil && errUdp == nil { 78 | atomic.StoreInt32(&w.state, StatusClosed) 79 | gslog.NewLog("webrtc_acceptor").Info("wx_acceptor stop") 80 | } 81 | } 82 | 83 | func (w *WebRTCAcceptor) ListenAndServe() { 84 | udpListener, err := net.ListenUDP("udp", &net.UDPAddr{ 85 | IP: net.IP{0, 0, 0, 0}, 86 | Port: w.config.UdpPort, 87 | }) 88 | if err != nil { 89 | gslog.NewLog("webrtc_acceptor").Error("Failed to listen udp", zap.Error(err)) 90 | panic(err) 91 | } 92 | w.udpListener = udpListener 93 | settingEngine := webrtc.SettingEngine{} 94 | settingEngine.DetachDataChannels() 95 | settingEngine.SetICEUDPMux(webrtc.NewICEUDPMux(nil, udpListener)) 96 | w.api = webrtc.NewAPI(webrtc.WithSettingEngine(settingEngine)) 97 | 98 | // exchange the answer and offer with the http 99 | httpListener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", w.config.HttpPort)) 100 | if err != nil { 101 | gslog.NewLog("webrtc_acceptor").Error("Failed to listen http", zap.Error(err)) 102 | panic(err) 103 | } 104 | w.httpListener = httpListener 105 | 106 | w.serve() 107 | } 108 | 109 | func adaptWebRTCHandler(handler *webRTCConnHandler) http.Handler { 110 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 111 | handler.ServeHTTP(w, r) 112 | }) 113 | } 114 | 115 | func (w *WebRTCAcceptor) serve() { 116 | defer w.Stop() 117 | adaptedHandler := adaptWebRTCHandler(&webRTCConnHandler{ 118 | api: w.api, 119 | config: w.config, 120 | connChan: w.connChan, 121 | SugaredLogger: gslog.NewLog("webrtc_handler").Sugar(), 122 | localAddr: w.udpListener.LocalAddr(), 123 | }) 124 | 125 | for _, group := range w.config.Groups { 126 | for _, middleware := range group.MiddlewareFunc { 127 | adaptedHandler = middleware(adaptedHandler) 128 | } 129 | } 130 | 131 | _ = http.Serve(w.httpListener, adaptedHandler) 132 | } 133 | -------------------------------------------------------------------------------- /acceptor/ws_acceptor.go: -------------------------------------------------------------------------------- 1 | package acceptor 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "os" 8 | "sync" 9 | "sync/atomic" 10 | 11 | "github.com/gorilla/websocket" 12 | "github.com/metagogs/gogs/gslog" 13 | "go.uber.org/zap" 14 | ) 15 | 16 | type WSAcceptor struct { 17 | connChan chan AcceptorConn 18 | listener net.Listener 19 | addr int 20 | config *AcceptorConfig 21 | state int32 22 | closeMutex sync.Mutex 23 | } 24 | 25 | func NewWSAcceptor(config *AcceptorConfig) *WSAcceptor { 26 | if len(config.Name) == 0 { 27 | gslog.NewLog("ws_acceptor").Error("name length is 0") 28 | os.Exit(1) 29 | } 30 | if len(config.Groups) == 0 { 31 | gslog.NewLog("ws_acceptor").Error("groups length is 0") 32 | os.Exit(1) 33 | } 34 | return &WSAcceptor{ 35 | addr: config.HttpPort, 36 | connChan: make(chan AcceptorConn, 50), 37 | config: config, 38 | } 39 | } 40 | 41 | func (w *WSAcceptor) GetConfig() *AcceptorConfig { 42 | return w.config 43 | } 44 | 45 | func (w *WSAcceptor) GetName() string { 46 | return w.config.Name 47 | } 48 | 49 | func (w *WSAcceptor) ListenAndServe() { 50 | var upgrader = websocket.Upgrader{ 51 | ReadBufferSize: 4096, 52 | WriteBufferSize: 4096, 53 | CheckOrigin: func(r *http.Request) bool { 54 | return true 55 | }, 56 | } 57 | 58 | listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", w.addr)) 59 | if err != nil { 60 | gslog.NewLog("ws_acceptor").Error("Failed to listen", zap.Error(err)) 61 | panic(err) 62 | } 63 | w.listener = listener 64 | 65 | w.serve(&upgrader) 66 | } 67 | 68 | func (w *WSAcceptor) Stop() { 69 | w.closeMutex.Lock() 70 | defer w.closeMutex.Unlock() 71 | 72 | if atomic.LoadInt32(&w.state) == StatusClosed { 73 | return 74 | } 75 | err := w.listener.Close() 76 | if err != nil { 77 | gslog.NewLog("ws_acceptor").Error("Failed to stop", zap.Error(err)) 78 | return 79 | } 80 | atomic.StoreInt32(&w.state, StatusClosed) 81 | gslog.NewLog("ws_acceptor").Info("ws_acceptor stop") 82 | } 83 | 84 | func (w *WSAcceptor) GetConnChan() chan AcceptorConn { 85 | return w.connChan 86 | } 87 | 88 | func (w *WSAcceptor) GetAddr() string { 89 | return fmt.Sprintf("%d[http]", w.config.HttpPort) 90 | } 91 | 92 | func (w *WSAcceptor) GetType() string { 93 | return ACCEPTOR_TYPE_WS 94 | } 95 | 96 | func adaptWSHandler(handler *wsConnHandler) http.Handler { 97 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 98 | handler.ServeHTTP(w, r) 99 | }) 100 | } 101 | 102 | func (w *WSAcceptor) serve(upgrader *websocket.Upgrader) { 103 | defer w.Stop() 104 | 105 | mux := http.NewServeMux() 106 | for _, group := range w.config.Groups { 107 | 108 | handler := &wsConnHandler{ 109 | upgrader: upgrader, 110 | connChan: w.connChan, 111 | SugaredLogger: gslog.NewLog("ws_handler").Sugar(), 112 | info: &ConnInfo{ 113 | AcceptorType: w.GetType(), 114 | AcceptorName: w.GetName(), 115 | AcceptorGroup: group.GroupName, 116 | BucketFillInterval: group.BucketFillInterval, 117 | BucketCapacity: group.BucketCapacity, 118 | }, 119 | } 120 | 121 | adaptedHandler := adaptWSHandler(handler) 122 | 123 | for _, middleware := range group.MiddlewareFunc { 124 | adaptedHandler = middleware(adaptedHandler) 125 | } 126 | 127 | mux.Handle("/"+group.GroupName, adaptedHandler) 128 | } 129 | _ = http.Serve(w.listener, mux) 130 | } 131 | -------------------------------------------------------------------------------- /acceptor/ws_conn.go: -------------------------------------------------------------------------------- 1 | package acceptor 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "sync/atomic" 7 | "time" 8 | 9 | "github.com/gorilla/websocket" 10 | "github.com/juju/ratelimit" 11 | ) 12 | 13 | type WSConn struct { 14 | state int32 15 | conn *websocket.Conn 16 | typ int 17 | reader io.Reader 18 | info *ConnInfo 19 | MaxMessagesInSecond uint 20 | bucket *ratelimit.Bucket 21 | } 22 | 23 | func NewWSConn(conn *websocket.Conn, info *ConnInfo) *WSConn { 24 | wsConn := &WSConn{ 25 | conn: conn, 26 | info: info, 27 | state: ConnStatusStart, 28 | } 29 | if info.BucketCapacity > 0 { 30 | wsConn.bucket = ratelimit.NewBucket(info.BucketFillInterval, info.BucketCapacity) 31 | } 32 | 33 | return wsConn 34 | } 35 | 36 | func (c *WSConn) SetCloseHandler(f func()) { 37 | c.conn.SetCloseHandler(func(code int, text string) error { 38 | f() 39 | return nil 40 | }) 41 | } 42 | 43 | func (c *WSConn) GetInfo() *ConnInfo { 44 | return c.info 45 | } 46 | 47 | // GetNextMessage reads the next message available in the stream 48 | func (c *WSConn) GetNextMessage() (b []byte, err error) { 49 | _, msgBytes, err := c.conn.ReadMessage() 50 | if err != nil { 51 | return nil, err 52 | } 53 | if c.info.BucketCapacity > 0 && c.bucket.TakeAvailable(1) < 1 { 54 | return nil, ErrMessageRateLimit 55 | } 56 | 57 | return msgBytes, nil 58 | } 59 | 60 | func (c *WSConn) Read(b []byte) (int, error) { 61 | if c.reader == nil { 62 | t, r, err := c.conn.NextReader() 63 | if err != nil { 64 | return 0, err 65 | } 66 | c.typ = t 67 | c.reader = r 68 | } 69 | n, err := c.reader.Read(b) 70 | if err != nil && err != io.EOF { 71 | return n, err 72 | } else if err == io.EOF { 73 | _, r, err := c.conn.NextReader() 74 | if err != nil { 75 | return 0, err 76 | } 77 | c.reader = r 78 | } 79 | 80 | return n, nil 81 | } 82 | 83 | func (c *WSConn) Write(b []byte) (int, error) { 84 | _ = c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) 85 | err := c.conn.WriteMessage(websocket.BinaryMessage, b) 86 | if err != nil { 87 | return 0, err 88 | } 89 | 90 | return len(b), nil 91 | } 92 | 93 | func (c *WSConn) IsClosed() bool { 94 | return atomic.LoadInt32(&c.state) == ConnStatusClosed 95 | } 96 | 97 | func (c *WSConn) Close() error { 98 | atomic.StoreInt32(&c.state, ConnStatusClosed) 99 | return c.conn.Close() 100 | } 101 | 102 | func (c *WSConn) LocalAddr() net.Addr { 103 | return c.conn.LocalAddr() 104 | } 105 | 106 | func (c *WSConn) RemoteAddr() net.Addr { 107 | return c.conn.RemoteAddr() 108 | } 109 | 110 | func (c *WSConn) SetDeadline(t time.Time) error { 111 | if err := c.SetReadDeadline(t); err != nil { 112 | return err 113 | } 114 | 115 | return c.SetWriteDeadline(t) 116 | } 117 | 118 | func (c *WSConn) SetReadDeadline(t time.Time) error { 119 | return c.conn.SetReadDeadline(t) 120 | } 121 | 122 | func (c *WSConn) SetWriteDeadline(t time.Time) error { 123 | return c.conn.SetWriteDeadline(t) 124 | } 125 | -------------------------------------------------------------------------------- /acceptor/ws_handler.go: -------------------------------------------------------------------------------- 1 | package acceptor 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gorilla/websocket" 7 | "github.com/metagogs/gogs/global" 8 | "github.com/metagogs/gogs/packet" 9 | "go.uber.org/zap" 10 | ) 11 | 12 | type wsConnHandler struct { 13 | upgrader *websocket.Upgrader 14 | connChan chan AcceptorConn 15 | *zap.SugaredLogger 16 | info *ConnInfo 17 | } 18 | 19 | func (h *wsConnHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { 20 | rw.Header().Set("Access-Control-Allow-Origin", "*") 21 | rw.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS") 22 | rw.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With") 23 | if r.Method == "OPTIONS" { 24 | return 25 | } 26 | 27 | conn, err := h.upgrader.Upgrade(rw, r, nil) 28 | if err != nil { 29 | h.Warnf("Upgrade failure, URI=%s, Error=%s", r.RequestURI, err.Error()) 30 | return 31 | } 32 | 33 | c := NewWSConn(conn, h.info) 34 | 35 | c.conn.SetReadLimit(packet.MaxPacketSize) 36 | 37 | h.connChan <- c 38 | 39 | if global.GoGSDebug { 40 | h.Info("Upgrade success", 41 | zap.String("name", h.info.AcceptorName), 42 | zap.String("group", h.info.AcceptorType), 43 | zap.String("host", r.Host), 44 | zap.String("remote_addr", r.RemoteAddr), 45 | zap.String("referer", r.Referer()), 46 | zap.String("uri", r.RequestURI)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /admin/admin.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "reflect" 8 | "runtime" 9 | 10 | "github.com/gin-gonic/gin" 11 | "github.com/metagogs/gogs/acceptor" 12 | "github.com/metagogs/gogs/component" 13 | "github.com/metagogs/gogs/config" 14 | "github.com/metagogs/gogs/gslog" 15 | "github.com/metagogs/gogs/session" 16 | "go.uber.org/zap" 17 | ) 18 | 19 | // AdminServer get the system running info 20 | type AdminServer struct { 21 | *gin.Engine 22 | *zap.SugaredLogger 23 | addr string 24 | debug bool 25 | config *config.Config 26 | sessionPool session.SessionPool 27 | decodeType map[string]uint8 28 | encodeType string 29 | status systemStatus 30 | } 31 | 32 | func NewAdminServer(config *config.Config, 33 | sessionPool session.SessionPool, 34 | decodeType map[string]uint8, 35 | encodeType string) *AdminServer { 36 | 37 | gin.SetMode(gin.ReleaseMode) 38 | r := gin.New() 39 | r.Use(gin.Recovery()) 40 | 41 | server := &AdminServer{ 42 | Engine: r, 43 | addr: fmt.Sprintf("0.0.0.0:%d", config.AdminPort), 44 | debug: config.Debug, 45 | config: config, 46 | SugaredLogger: gslog.NewLog("admin").Sugar(), 47 | sessionPool: sessionPool, 48 | decodeType: decodeType, 49 | encodeType: encodeType, 50 | status: systemStatus{ 51 | Acceptors: make(map[string]string), 52 | }, 53 | } 54 | server.Init() 55 | 56 | return server 57 | } 58 | 59 | func (a *AdminServer) Init() { 60 | a.GET("/ping", func(c *gin.Context) { 61 | c.JSON(200, gin.H{ 62 | "message": "pong", 63 | }) 64 | }) 65 | // for k8s health probe 66 | a.Any("/health", func(c *gin.Context) { 67 | c.JSON(http.StatusOK, map[string]string{"status": "ok"}) 68 | }) 69 | 70 | if a.debug { 71 | // in the debug mode, show the more system info 72 | a.GET("/admin", a.systemStatus) 73 | } 74 | } 75 | 76 | func (a *AdminServer) Start() { 77 | a.Infof("admin server start on %s", a.addr) 78 | go func() { 79 | _ = a.Run(a.addr) 80 | }() 81 | } 82 | 83 | func (a *AdminServer) systemStatus(g *gin.Context) { 84 | a.Info("request system status") 85 | 86 | a.status.DebugMode = a.config.Debug 87 | a.status.DecodeType = a.decodeType 88 | a.status.EncodeType = a.encodeType 89 | a.status.SessionCount = a.sessionPool.GetSessionTotalCount() // total session connected include disconnect in the system 90 | a.status.OnlineSessionCount = a.sessionPool.GetSessionCount() // current connected session count 91 | a.status.NumGoroutine = runtime.NumGoroutine() 92 | a.status.NumCPU = runtime.NumCPU() 93 | a.status.NumCgoCall = runtime.NumCgoCall() 94 | 95 | m := runtime.MemStats{} 96 | runtime.ReadMemStats(&m) 97 | a.status.Memory = systemMemory{ 98 | Alloc: m.Alloc, 99 | TotalAlloc: m.TotalAlloc, 100 | Sys: m.Sys, 101 | Lookups: m.Lookups, 102 | Mallocs: m.Mallocs, 103 | Frees: m.Frees, 104 | HeapAlloc: m.HeapAlloc, 105 | HeapSys: m.HeapSys, 106 | HeapIdle: m.HeapIdle, 107 | HeapInuse: m.HeapInuse, 108 | HeapReleased: m.HeapReleased, 109 | HeapObjects: m.HeapObjects, 110 | StackInuse: m.StackInuse, 111 | StackSys: m.StackSys, 112 | } 113 | 114 | a.status.Env = os.Environ() 115 | 116 | g.JSON(200, a.status) 117 | } 118 | 119 | func (a *AdminServer) RegisterComponent(sd component.ComponentDesc, ss interface{}) { 120 | a.status.Components = append(a.status.Components, sd) 121 | } 122 | 123 | func (a *AdminServer) AddAcceptor(acceptor acceptor.Acceptor) { 124 | a.status.Acceptors[reflect.TypeOf(acceptor).Elem().Name()] = acceptor.GetAddr() 125 | } 126 | -------------------------------------------------------------------------------- /admin/status.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import "github.com/metagogs/gogs/component" 4 | 5 | type systemMemory struct { 6 | Alloc uint64 `json:"alloc"` 7 | TotalAlloc uint64 `json:"total_alloc"` 8 | Sys uint64 `json:"sys"` 9 | Lookups uint64 `json:"lookups"` 10 | Mallocs uint64 `json:"mallocs"` 11 | Frees uint64 `json:"frees"` 12 | HeapAlloc uint64 `json:"heap_alloc"` 13 | HeapSys uint64 `json:"heap_sys"` 14 | HeapIdle uint64 `json:"heap_idle"` 15 | HeapInuse uint64 `json:"heap_inuse"` 16 | HeapReleased uint64 `json:"heap_released"` 17 | HeapObjects uint64 `json:"heap_objects"` 18 | StackInuse uint64 `json:"stack_inuse"` 19 | StackSys uint64 `json:"stack_sys"` 20 | } 21 | 22 | type systemStatus struct { 23 | Running bool `json:"running"` 24 | DebugMode bool `json:"debug_mode"` 25 | SessionCount int64 `json:"session_count"` 26 | OnlineSessionCount int64 `json:"online_session_count"` 27 | NumGoroutine int `json:"num_goroutine"` 28 | NumCgoCall int64 `json:"num_cgo_call"` 29 | NumCPU int `json:"num_cpu"` 30 | SystemLatency int64 `json:"system_latency"` 31 | SystemLatencyList []int64 `json:"system_latency_list"` 32 | UserLatency map[int64]int64 `json:"user_latency"` 33 | Acceptors map[string]string `json:"acceptors"` 34 | EncodeType string `json:"encode_type"` 35 | DecodeType map[string]uint8 `json:"decode_type"` 36 | Components []component.ComponentDesc `json:"components"` 37 | Memory systemMemory `json:"memory"` 38 | Env []string `json:"env"` 39 | } 40 | -------------------------------------------------------------------------------- /agent/constants.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrCloseClosedSession = errors.New("close closed session") 7 | ) 8 | 9 | const ( 10 | _ int32 = iota 11 | // StatusStart status 12 | StatusStart 13 | // StatusHandshake status 14 | StatusHandshake 15 | // StatusWorking status 16 | StatusWorking 17 | // StatusClosed status 18 | StatusClosed 19 | ) 20 | -------------------------------------------------------------------------------- /agent/factory.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "sync" 7 | "time" 8 | 9 | "github.com/bwmarrin/snowflake" 10 | "github.com/metagogs/gogs/acceptor" 11 | "github.com/metagogs/gogs/config" 12 | "github.com/metagogs/gogs/gslog" 13 | "github.com/metagogs/gogs/message" 14 | "github.com/metagogs/gogs/proto" 15 | "github.com/metagogs/gogs/session" 16 | "github.com/metagogs/gogs/utils/bytebuffer" 17 | "github.com/metagogs/gogs/utils/snow" 18 | "go.uber.org/zap" 19 | ) 20 | 21 | var ( 22 | pingPool = &sync.Pool{ 23 | New: func() interface{} { 24 | return &proto.Ping{} 25 | }, 26 | } 27 | ) 28 | 29 | // AgentFactory is the factory to create the agent 30 | type AgentFactory struct { 31 | config *config.Config 32 | sf *snowflake.Node // snowflake node 33 | sessionPool session.SessionPool // session pool 34 | messageServer *message.MessageServer // message server 35 | messagesBufferSize int // message buffer size 36 | } 37 | 38 | func NewAgentFactory(config *config.Config, 39 | pool session.SessionPool, 40 | messageServer *message.MessageServer) *AgentFactory { 41 | 42 | sf, err := snow.NewSnowNode() 43 | if err != nil { 44 | fmt.Println(err.Error()) 45 | os.Exit(1) 46 | } 47 | return &AgentFactory{ 48 | config: config, 49 | sf: sf, 50 | sessionPool: pool, 51 | messageServer: messageServer, 52 | messagesBufferSize: config.AgentMessageBufferSize, 53 | } 54 | } 55 | 56 | func (af *AgentFactory) NewAgent(conn acceptor.AcceptorConn) *Agent { 57 | agentId := af.sf.Generate().Int64() 58 | agent := &Agent{ 59 | AgentID: agentId, 60 | conn: conn, 61 | agentLog: gslog.NewLog("agent").With(zap.Int64("agent_id", agentId)), 62 | chSend: make(chan *bytebuffer.ByteBuffer, af.messagesBufferSize), 63 | chSendByte: make(chan []byte, af.messagesBufferSize), 64 | chStopWrite: make(chan struct{}), 65 | chStopHeartbeat: make(chan struct{}), 66 | chDie: make(chan struct{}), 67 | messageServer: af.messageServer, 68 | heartbeatTimeout: time.Duration(af.config.AgentHeartBeatTimeout) * time.Second, 69 | heartbeatLog: af.config.AgentHeartBeatLog, 70 | } 71 | // create session 72 | s := af.sessionPool.CreateSession(agent) 73 | agent.sess = s 74 | 75 | return agent 76 | } 77 | -------------------------------------------------------------------------------- /builder.go: -------------------------------------------------------------------------------- 1 | package gogs 2 | 3 | import ( 4 | "github.com/metagogs/gogs/admin" 5 | "github.com/metagogs/gogs/codec" 6 | "github.com/metagogs/gogs/config" 7 | "github.com/metagogs/gogs/dispatch" 8 | "github.com/metagogs/gogs/group" 9 | "github.com/metagogs/gogs/message" 10 | "github.com/metagogs/gogs/session" 11 | "github.com/metagogs/gogs/webserver" 12 | ) 13 | 14 | type builder struct { 15 | sessionPool session.SessionPool // session池管理 16 | groupServer *group.GroupServer // 组管理 17 | adminServer *admin.AdminServer // 服务管理 18 | messageServer *message.MessageServer // 消息管理,包装编码和分发 19 | webServer *webserver.WebServer // web服务管理 20 | } 21 | 22 | func NewBuilder(config *config.Config) *builder { 23 | webserver := newWebServer(config) 24 | sessionPool := newSessionPool(config) 25 | groupServer := newGroupServer(config) 26 | dispatchServer := newDispatchServer(config) 27 | codecHelper := newCodecHelper(config, dispatchServer) 28 | adminServer := newAdminServer(config, sessionPool, codecHelper) 29 | messageServer := newMessageServer(config, codecHelper, dispatchServer) 30 | 31 | return &builder{ 32 | sessionPool: sessionPool, 33 | groupServer: groupServer, 34 | adminServer: adminServer, 35 | messageServer: messageServer, 36 | webServer: webserver, 37 | } 38 | } 39 | 40 | func newSessionPool(config *config.Config) session.SessionPool { 41 | // session池管理 42 | appSessionPool := session.NewSessionPool(config) 43 | DefaultSessionPool = appSessionPool 44 | return appSessionPool 45 | } 46 | 47 | func newDispatchServer(config *config.Config) *dispatch.DispatchServer { 48 | // 消息和方法分发管理 49 | return dispatch.NewDispatchServer() 50 | } 51 | 52 | func newGroupServer(config *config.Config) *group.GroupServer { 53 | return group.NewGroupServer() 54 | } 55 | 56 | func newCodecHelper(config *config.Config, dispatchServer *dispatch.DispatchServer) *codec.CodecHelper { 57 | return codec.NewCodecHelper(config, dispatchServer) 58 | } 59 | 60 | func newAdminServer(config *config.Config, sessionPool session.SessionPool, codecHelper *codec.CodecHelper) *admin.AdminServer { 61 | d, e := codecHelper.GetTypes() 62 | return admin.NewAdminServer(config, sessionPool, d, e) 63 | } 64 | 65 | func newMessageServer(config *config.Config, codecHelper *codec.CodecHelper, dispatchServer *dispatch.DispatchServer) *message.MessageServer { 66 | messageServer := message.NewMessageServer(codecHelper, dispatchServer) 67 | DefaultMessageServer = messageServer 68 | return messageServer 69 | } 70 | 71 | func newWebServer(config *config.Config) *webserver.WebServer { 72 | return webserver.NewWebServer(config) 73 | } 74 | -------------------------------------------------------------------------------- /codec/benchmark_test.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/metagogs/gogs/config" 8 | ) 9 | 10 | // go test -bench=. -benchmem -benchtime=20s 11 | // go test -bench=BenchmarkEncodePacketProto -run=none -benchmem 12 | // go test -bench=BenchmarkEncodePacketProto -run=none -benchmem -memprofile=mem.pprof -cpuprofile=cpu.pprof 13 | // go tool pprof -http=:8081 cpu.pprof 14 | func BenchmarkEncodePacketProto(b *testing.B) { 15 | dispatch := TestDispatch{} 16 | d := NewCodecHelper(config.NewDefaultConfig(), &dispatch) 17 | d.RegisterEncode(CodecProtoData, &ProtoEncode{}) 18 | pingTest1 := &Ping{ 19 | Time: "123", 20 | } 21 | 22 | b.ResetTimer() 23 | for i := 0; i < b.N; i++ { 24 | _, _ = d.Encode(pingTest1) 25 | } 26 | } 27 | 28 | func BenchmarkEncodePacketJson(b *testing.B) { 29 | dispatch := TestDispatch{} 30 | d := NewCodecHelper(config.NewDefaultConfig(), &dispatch) 31 | d.RegisterEncode(CodecJSONData, &JSONEncode{}) 32 | pingTest1 := &Ping{ 33 | Time: "123", 34 | } 35 | 36 | b.ResetTimer() 37 | for i := 0; i < b.N; i++ { 38 | _, _ = d.Encode(pingTest1) 39 | } 40 | } 41 | 42 | func BenchmarkEncodePacketJsonNoHeader(b *testing.B) { 43 | dispatch := TestDispatch{} 44 | d := NewCodecHelper(config.NewDefaultConfig(), &dispatch) 45 | d.RegisterEncode(CodecJSONDataNoHeader, &JSONEncode{}) 46 | pingTest1 := &Ping{ 47 | Time: "123", 48 | } 49 | 50 | b.ResetTimer() 51 | for i := 0; i < b.N; i++ { 52 | _, _ = d.Encode(pingTest1) 53 | } 54 | } 55 | 56 | func BenchmarkJSONOriginEncode(b *testing.B) { 57 | b.ResetTimer() 58 | for i := 0; i < b.N; i++ { 59 | _, _ = json.Marshal(&Ping{ 60 | Time: "1234567890", 61 | }) 62 | } 63 | } 64 | 65 | func BenchmarkJSONEncode(b *testing.B) { 66 | encode := &JSONEncode{} 67 | b.ResetTimer() 68 | for i := 0; i < b.N; i++ { 69 | _, _ = encode.Encode(&Ping{ 70 | Time: "1234567890", 71 | }) 72 | } 73 | } 74 | 75 | func BenchmarkProtoEncode(b *testing.B) { 76 | encode := &ProtoEncode{} 77 | b.ResetTimer() 78 | for i := 0; i < b.N; i++ { 79 | _, _ = encode.Encode(&Ping{ 80 | Time: "1234567890", 81 | }) 82 | } 83 | } 84 | 85 | func BenchmarkJSONOriginDecode(b *testing.B) { 86 | data, _ := json.Marshal(&Ping{ 87 | Time: "1234567890", 88 | }) 89 | b.ResetTimer() 90 | for i := 0; i < b.N; i++ { 91 | _ = json.Unmarshal(data, &Ping{}) 92 | } 93 | } 94 | 95 | func BenchmarkJSONDecode(b *testing.B) { 96 | encode := &JSONEncode{} 97 | decode := &JSONDecode{} 98 | 99 | data, _ := encode.Encode(&Ping{ 100 | Time: "1234567890", 101 | }) 102 | b.ResetTimer() 103 | for i := 0; i < b.N; i++ { 104 | _ = decode.Decode(data, &Ping{}) 105 | } 106 | } 107 | 108 | func BenchmarkProtoDecode(b *testing.B) { 109 | encode := &ProtoEncode{} 110 | decode := &ProtoDecode{} 111 | 112 | data, _ := encode.Encode(&Ping{ 113 | Time: "1234567890", 114 | }) 115 | 116 | b.ResetTimer() 117 | for i := 0; i < b.N; i++ { 118 | _ = decode.Decode(data, &Ping{}) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /codec/constants.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrActionNotFound = errors.New("action not found") 7 | ErrMethodNotFound = errors.New("method not found") 8 | ErrMessageDecode = errors.New("message decode error") 9 | ErrRegisterCodecType = errors.New("register codec type error") 10 | ErrRegisterCodecTypeExist = errors.New("register codec type exist") 11 | ErrCodecType = errors.New("codec type error") 12 | ErrNotValidJSONType = errors.New("not valid json type") 13 | ErrActionNotExist = errors.New("action not exist") 14 | ErrInvalidProtoMessage = errors.New("invalid proto message") 15 | ) 16 | 17 | const ( 18 | CodecJSONDataNoHeader = uint8(0) // 标准协议头的JSON 19 | CodecJSONData = uint8(1) // 标准协议头的JSON 20 | CodecProtoData = uint8(2) // 使用proto解码 21 | 22 | CurrentMaxCodecType = uint8(2) // 当前最大编码类型 23 | MaxCodecType = uint8(7) // 最大编码类型 24 | ) 25 | -------------------------------------------------------------------------------- /codec/json_decode.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type JSONDecode struct{} 8 | 9 | func (c *JSONDecode) Decode(data []byte, in interface{}) error { 10 | if json.Valid(data) { 11 | return json.Unmarshal(data, in) 12 | } 13 | return ErrNotValidJSONType 14 | } 15 | 16 | func (c *JSONDecode) String(in interface{}) string { 17 | msg, err := json.Marshal(in) 18 | if err != nil { 19 | return "proto encode error, " + err.Error() 20 | } 21 | 22 | return string(msg) 23 | } 24 | -------------------------------------------------------------------------------- /codec/json_encode.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type JSONEncode struct{} 8 | 9 | func (c *JSONEncode) Encode(in interface{}) ([]byte, error) { 10 | return json.Marshal(in) 11 | } 12 | 13 | func (c *JSONEncode) String(in interface{}) string { 14 | msg, err := json.Marshal(in) 15 | if err != nil { 16 | return "proto encode error, " + err.Error() 17 | } 18 | 19 | return string(msg) 20 | } 21 | -------------------------------------------------------------------------------- /codec/proto_decode.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "google.golang.org/protobuf/encoding/protojson" 5 | "google.golang.org/protobuf/proto" 6 | ) 7 | 8 | type ProtoDecode struct{} 9 | 10 | func (c *ProtoDecode) Decode(data []byte, in interface{}) error { 11 | pb, ok := in.(proto.Message) 12 | if !ok { 13 | return ErrInvalidProtoMessage 14 | } 15 | return proto.Unmarshal(data, pb) 16 | } 17 | 18 | func (c *ProtoDecode) String(in interface{}) string { 19 | pb, ok := in.(proto.Message) 20 | if !ok { 21 | return "proto encode error, is not valid proto message" 22 | } 23 | 24 | msg, err := protojson.MarshalOptions{EmitUnpopulated: true}.Marshal(pb) 25 | if err != nil { 26 | return "proto encode error, " + err.Error() 27 | } 28 | 29 | return string(msg) 30 | } 31 | -------------------------------------------------------------------------------- /codec/proto_encode.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "google.golang.org/protobuf/encoding/protojson" 5 | "google.golang.org/protobuf/proto" 6 | ) 7 | 8 | type ProtoEncode struct{} 9 | 10 | func (c *ProtoEncode) Encode(in interface{}) ([]byte, error) { 11 | pb, ok := in.(proto.Message) 12 | if !ok { 13 | return nil, ErrInvalidProtoMessage 14 | } 15 | return proto.Marshal(pb) 16 | } 17 | 18 | func (c *ProtoEncode) String(in interface{}) string { 19 | pb, ok := in.(proto.Message) 20 | if !ok { 21 | return "proto encode error, is not valid proto message" 22 | } 23 | 24 | msg, err := protojson.MarshalOptions{EmitUnpopulated: true}.Marshal(pb) 25 | if err != nil { 26 | return "proto encode error, " + err.Error() 27 | } 28 | 29 | return string(msg) 30 | } 31 | -------------------------------------------------------------------------------- /codec/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | option go_package = "/test"; 5 | 6 | 7 | // @gogs:Request 8 | message Ping { 9 | string time = 1; 10 | } 11 | 12 | message Pong { 13 | string time = 1; 14 | } -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | threshold: 0% 6 | branches: 7 | - main 8 | patch: 9 | default: 10 | threshold: 0% 11 | branches: 12 | - main 13 | 14 | comment: 15 | layout: reach, diff, flags, files 16 | behavior: default 17 | require_changes: false 18 | branches: 19 | - main 20 | 21 | ignore: 22 | - "LICENSES" 23 | - "Makefile" 24 | - ".gitignore" 25 | - "**/*.sh" 26 | - "**/*.pb.go" 27 | - "**/*_test.go" 28 | - ".git" 29 | - "*.yml" 30 | - "*.yaml" 31 | - "*.md" 32 | - "*.tpl" 33 | - "docs/.*" 34 | - "**/*.proto" 35 | - "e2e/testdata/.*" 36 | - "e2e/smkoe/.*" 37 | - "e2e/helper.go" 38 | - "tools/gogs/gen/testdata/.*" 39 | - "examples/.*" -------------------------------------------------------------------------------- /component/component.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | 7 | "github.com/metagogs/gogs/session" 8 | ) 9 | 10 | // methodHandler is the handler of component method 11 | // 12 | // func _BaseWorldComponent_BindUser_Handler(srv interface{}, ctx context.Context, sess *session.Session, in interface{}) { 13 | // srv.(Component).BindUser(ctx, sess, in.(*BindUser)) 14 | // } 15 | type methodHandler func(srv interface{}, ctx context.Context, sess *session.Session, in interface{}) 16 | 17 | // return the field instance 18 | // it's better to use the filed handler to create the filed instance 19 | // 20 | // func() interface{} { 21 | // return new(BindUser) 22 | // } 23 | type fieldHandler func() interface{} 24 | 25 | type ComponentDesc struct { 26 | ComponentName string 27 | ComponentIndex uint8 // equal to module index 28 | ComponentType interface{} `json:"-"` 29 | Methods []ComponentMethodDesc 30 | } 31 | 32 | type ComponentMethodDesc struct { 33 | MethodIndex uint32 // equal to action index 34 | MethodName string // equal to action name 35 | FieldType reflect.Type `json:"-"` // method field type to create field by reflect, but use the filed handler is better 36 | Handler methodHandler `json:"-"` // method handler 37 | FieldHandler fieldHandler `json:"-"` // method field handler, the handler function will be called to create field 38 | } 39 | 40 | type Component interface{} 41 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | type Config struct { 12 | Debug bool // debug mode 13 | Gopprof bool // start pprof 14 | GopprofAddr int // the pprof server http port 15 | AdminPort int // admin server http port and used for health check 16 | 17 | AgentMessageBufferSize int // agent message buffer size 18 | AgentHeartBeatTimeout int // agent heartbeat timeout, second 19 | AgentHeartBeatLog bool // show the agent heartbeat log 20 | 21 | SendMessageLog bool // show the send message log 22 | ReceiveMessageLog bool // show the receive message log 23 | 24 | StaredCallback func() // just for testing 25 | } 26 | 27 | func NewDefaultConfig() *Config { 28 | return &Config{ 29 | Debug: false, 30 | Gopprof: false, 31 | GopprofAddr: 9998, 32 | AdminPort: 9999, 33 | 34 | AgentMessageBufferSize: 1000, 35 | AgentHeartBeatTimeout: 20, 36 | AgentHeartBeatLog: false, 37 | 38 | SendMessageLog: false, 39 | ReceiveMessageLog: false, 40 | } 41 | } 42 | 43 | func setDefaultConfig() { 44 | config := NewDefaultConfig() 45 | defaultsMap := map[string]interface{}{ 46 | "debug": config.Debug, 47 | "gopprof": config.Gopprof, 48 | "gopprofaddr": config.GopprofAddr, 49 | "adminport": config.AdminPort, 50 | "agentmessagebuffersize": config.AgentMessageBufferSize, 51 | "agentheartbeattimeout": config.AgentHeartBeatTimeout, 52 | "agentheartbeatlog": config.AgentHeartBeatLog, 53 | "sendmessagelog": config.SendMessageLog, 54 | "receivemessagelog": config.ReceiveMessageLog, 55 | } 56 | 57 | for param := range defaultsMap { 58 | if viper.Get(param) == nil { 59 | viper.SetDefault(param, defaultsMap[param]) 60 | } 61 | } 62 | } 63 | 64 | func NewConfig(file ...string) *Config { 65 | if len(file) > 0 { 66 | viper.SetConfigFile(file[0]) 67 | } else { 68 | viper.SetConfigFile("config.yaml") 69 | } 70 | if err := viper.ReadInConfig(); err != nil { 71 | fmt.Println(err) 72 | os.Exit(1) 73 | } 74 | viper.SetEnvPrefix("GOGS") 75 | viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_")) 76 | setDefaultConfig() 77 | viper.AutomaticEnv() 78 | 79 | var config *Config 80 | if err := viper.Unmarshal(&config); err != nil { 81 | fmt.Println(err) 82 | os.Exit(1) 83 | } 84 | 85 | return config 86 | } 87 | -------------------------------------------------------------------------------- /config/config.yaml: -------------------------------------------------------------------------------- 1 | Debug: true 2 | AgentHeartBeatTimeout: 19 -------------------------------------------------------------------------------- /config/config2.yaml: -------------------------------------------------------------------------------- 1 | Debug: true 2 | AgentHeartBeatTimeout: 19 -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestNewConfig(t *testing.T) { 11 | os.Setenv("GOGS_GOPPROFADDR", "9996") 12 | 13 | config := NewConfig() 14 | assert.Equal(t, true, config.Debug) 15 | assert.Equal(t, 9996, config.GopprofAddr) 16 | assert.Equal(t, 19, config.AgentHeartBeatTimeout) 17 | 18 | config = NewConfig("config2.yaml") 19 | assert.Equal(t, true, config.Debug) 20 | assert.Equal(t, 9996, config.GopprofAddr) 21 | assert.Equal(t, 19, config.AgentHeartBeatTimeout) 22 | } 23 | -------------------------------------------------------------------------------- /deployment/deployment.go: -------------------------------------------------------------------------------- 1 | package deployment 2 | 3 | import ( 4 | _ "embed" 5 | "os" 6 | 7 | "github.com/metagogs/gogs/acceptor" 8 | "github.com/metagogs/gogs/config" 9 | "github.com/metagogs/gogs/utils/templatex" 10 | "github.com/pterm/pterm" 11 | ) 12 | 13 | //go:embed deployment.tpl 14 | var DeploymentTpl string 15 | 16 | type DeploymentInfo struct { 17 | Name string 18 | Port int 19 | Protocol string 20 | Svc bool 21 | } 22 | 23 | type DeploymentHelper struct { 24 | Infos []DeploymentInfo 25 | Svc bool // use the svc not hostport 26 | Config *config.Config 27 | Name string 28 | Namespace string 29 | } 30 | 31 | func NewDeploymentHelper(config *config.Config, svc bool, name string, namespace string) *DeploymentHelper { 32 | d := &DeploymentHelper{ 33 | Svc: svc, 34 | Config: config, 35 | Name: name, 36 | Namespace: namespace, 37 | } 38 | if len(d.Name) == 0 { 39 | d.Name = "gogs" 40 | } 41 | if len(d.Namespace) == 0 { 42 | d.Namespace = "gogs" 43 | } 44 | d.Infos = append(d.Infos, DeploymentInfo{ 45 | Name: "admin-tcp", 46 | Port: config.AdminPort, 47 | Protocol: "TCP", 48 | Svc: svc, 49 | }) 50 | 51 | return d 52 | } 53 | 54 | func (d *DeploymentHelper) AddAcceptor(config *acceptor.AcceptorConfig) { 55 | if config.UdpPort != 0 { 56 | d.Infos = append(d.Infos, DeploymentInfo{ 57 | Name: config.Name + "-udp", 58 | Port: config.UdpPort, 59 | Protocol: "UDP", 60 | Svc: d.Svc, 61 | }) 62 | } 63 | if config.HttpPort != 0 { 64 | d.Infos = append(d.Infos, DeploymentInfo{ 65 | Name: config.Name + "-tcp", 66 | Port: config.HttpPort, 67 | Protocol: "TCP", 68 | Svc: d.Svc, 69 | }) 70 | } 71 | } 72 | 73 | func (d *DeploymentHelper) Generate() error { 74 | fileName := "deployment.yaml" 75 | if _, err := os.Stat(fileName); err == nil { 76 | pterm.Error.Printfln(fileName + " already exists") 77 | return nil 78 | } 79 | data := map[string]interface{}{} 80 | data["Deployments"] = d.Infos 81 | data["Svc"] = d.Svc 82 | data["HealthPort"] = d.Config.AdminPort 83 | data["PodName"] = d.Name 84 | data["PodNamespace"] = d.Namespace 85 | 86 | if err := templatex.With("gogs").Parse(DeploymentTpl).SaveTo(data, fileName, false); err != nil { 87 | pterm.Error.Printfln("generate file error :" + err.Error()) 88 | return err 89 | } 90 | 91 | pterm.Success.Printfln("generate file success " + fileName + " and you should edit the container image with your own image") 92 | 93 | return nil 94 | } 95 | -------------------------------------------------------------------------------- /deployment/deployment.tpl: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: {{.PodName}} 5 | namespace: {{.PodNamespace}} 6 | labels: 7 | app: {{.PodName}} 8 | spec: 9 | containers: 10 | - name: gogs-main 11 | image: gogs-image 12 | ports: 13 | {{range .Deployments }} 14 | - name: {{.Name}} 15 | containerPort: {{.Port}} 16 | protocol: {{.Protocol}} 17 | {{if not .Svc}}hostPort: {{.Port}}{{end}} 18 | {{end}} 19 | resources: 20 | limits: 21 | cpu: 500m 22 | memory: 500Mi 23 | requests: 24 | cpu: 100m 25 | memory: 100Mi 26 | readinessProbe: 27 | httpGet: 28 | path: /health 29 | port: {{.HealthPort}} 30 | scheme: HTTP 31 | initialDelaySeconds: 5 32 | timeoutSeconds: 1 33 | periodSeconds: 5 34 | successThreshold: 1 35 | failureThreshold: 3 36 | imagePullPolicy: IfNotPresent 37 | restartPolicy: Always 38 | 39 | {{if .Svc}} 40 | --- 41 | apiVersion: v1 42 | kind: Service 43 | metadata: 44 | labels: 45 | app: {{.PodName}} 46 | name: {{.PodName}}-svc 47 | namespace: {{.PodNamespace}} 48 | spec: 49 | ports: 50 | {{range .Deployments }} 51 | - port: {{.Port}} 52 | targetPort: {{.Port}} 53 | protocol: {{.Protocol}} 54 | {{end}} 55 | selector: 56 | app: {{.PodName}} 57 | type: ClusterIP 58 | 59 | {{end}} -------------------------------------------------------------------------------- /deployment/deployment_test.go: -------------------------------------------------------------------------------- 1 | package deployment 2 | 3 | import ( 4 | _ "embed" 5 | "os" 6 | "testing" 7 | 8 | "github.com/metagogs/gogs/config" 9 | "github.com/metagogs/gogs/utils/filex" 10 | ) 11 | 12 | func TestDeploymentHelper_Generate(t *testing.T) { 13 | type fields struct { 14 | Infos []DeploymentInfo 15 | Svc bool 16 | Config *config.Config 17 | Name string 18 | Namespace string 19 | } 20 | tests := []struct { 21 | name string 22 | fields fields 23 | wantErr bool 24 | }{ 25 | { 26 | name: "test1.yaml", 27 | fields: fields{ 28 | Infos: []DeploymentInfo{ 29 | { 30 | Name: "admin-tcp", 31 | Port: 8080, 32 | Protocol: "TCP", 33 | Svc: true, 34 | }, 35 | { 36 | Name: "admin-udp", 37 | Port: 8081, 38 | Protocol: "UDP", 39 | Svc: true, 40 | }, 41 | }, 42 | Config: &config.Config{ 43 | AdminPort: 8080, 44 | }, 45 | Svc: true, 46 | Name: "testname", 47 | Namespace: "testnamespace", 48 | }, 49 | wantErr: false, 50 | }, 51 | { 52 | name: "test2.yaml", 53 | fields: fields{ 54 | Infos: []DeploymentInfo{ 55 | { 56 | Name: "admin-tcp", 57 | Port: 8080, 58 | Protocol: "TCP", 59 | Svc: false, 60 | }, 61 | { 62 | Name: "admin-udp", 63 | Port: 8081, 64 | Protocol: "UDP", 65 | Svc: false, 66 | }, 67 | }, 68 | Config: &config.Config{ 69 | AdminPort: 8080, 70 | }, 71 | Svc: false, 72 | Name: "testname", 73 | Namespace: "testnamespace", 74 | }, 75 | wantErr: false, 76 | }, 77 | } 78 | for _, tt := range tests { 79 | t.Run(tt.name, func(t *testing.T) { 80 | d := &DeploymentHelper{ 81 | Infos: tt.fields.Infos, 82 | Svc: tt.fields.Svc, 83 | Config: tt.fields.Config, 84 | Name: tt.fields.Name, 85 | Namespace: tt.fields.Namespace, 86 | } 87 | if err := d.Generate(); (err != nil) != tt.wantErr { 88 | t.Errorf("DeploymentHelper.Generate() error = %v, wantErr %v", err, tt.wantErr) 89 | } 90 | 91 | if ok := filex.IsFileEqual("testdata/"+tt.name, "deployment.yaml"); !ok { 92 | t.Errorf("Init.Generate() error = %s is not equal to %s", "testdata/"+tt.name, "deployment.yaml") 93 | } 94 | _ = os.Remove("deployment.yaml") 95 | }) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /deployment/testdata/test1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: testname 5 | namespace: testnamespace 6 | labels: 7 | app: testname 8 | spec: 9 | containers: 10 | - name: gogs-main 11 | image: gogs-image 12 | ports: 13 | 14 | - name: admin-tcp 15 | containerPort: 8080 16 | protocol: TCP 17 | 18 | 19 | - name: admin-udp 20 | containerPort: 8081 21 | protocol: UDP 22 | 23 | 24 | resources: 25 | limits: 26 | cpu: 500m 27 | memory: 500Mi 28 | requests: 29 | cpu: 100m 30 | memory: 100Mi 31 | readinessProbe: 32 | httpGet: 33 | path: /health 34 | port: 8080 35 | scheme: HTTP 36 | initialDelaySeconds: 5 37 | timeoutSeconds: 1 38 | periodSeconds: 5 39 | successThreshold: 1 40 | failureThreshold: 3 41 | imagePullPolicy: IfNotPresent 42 | restartPolicy: Always 43 | 44 | 45 | --- 46 | apiVersion: v1 47 | kind: Service 48 | metadata: 49 | labels: 50 | app: testname 51 | name: testname-svc 52 | namespace: testnamespace 53 | spec: 54 | ports: 55 | 56 | - port: 8080 57 | targetPort: 8080 58 | protocol: TCP 59 | 60 | - port: 8081 61 | targetPort: 8081 62 | protocol: UDP 63 | 64 | selector: 65 | app: testname 66 | type: ClusterIP 67 | 68 | -------------------------------------------------------------------------------- /deployment/testdata/test2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: testname 5 | namespace: testnamespace 6 | labels: 7 | app: testname 8 | spec: 9 | containers: 10 | - name: gogs-main 11 | image: gogs-image 12 | ports: 13 | 14 | - name: admin-tcp 15 | containerPort: 8080 16 | protocol: TCP 17 | hostPort: 8080 18 | 19 | - name: admin-udp 20 | containerPort: 8081 21 | protocol: UDP 22 | hostPort: 8081 23 | 24 | resources: 25 | limits: 26 | cpu: 500m 27 | memory: 500Mi 28 | requests: 29 | cpu: 100m 30 | memory: 100Mi 31 | readinessProbe: 32 | httpGet: 33 | path: /health 34 | port: 8080 35 | scheme: HTTP 36 | initialDelaySeconds: 5 37 | timeoutSeconds: 1 38 | periodSeconds: 5 39 | successThreshold: 1 40 | failureThreshold: 3 41 | imagePullPolicy: IfNotPresent 42 | restartPolicy: Always 43 | 44 | -------------------------------------------------------------------------------- /dispatch/constants.go: -------------------------------------------------------------------------------- 1 | package dispatch 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrObjectActionNotFound = errors.New("object action not found") 7 | ErrMethodNotFound = errors.New("method not found") 8 | ) 9 | -------------------------------------------------------------------------------- /dispatch/method.go: -------------------------------------------------------------------------------- 1 | package dispatch 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | 7 | "github.com/metagogs/gogs/component" 8 | "github.com/metagogs/gogs/session" 9 | ) 10 | 11 | type serverMethod struct { 12 | srv interface{} 13 | componentDesc *component.ComponentDesc 14 | methodDesc *component.ComponentMethodDesc 15 | fieldType reflect.Type 16 | fieldHandler func() interface{} 17 | } 18 | 19 | func (s *serverMethod) GetSrv() interface{} { 20 | return s.srv 21 | } 22 | 23 | func (s *serverMethod) GetMethodDesc() *component.ComponentMethodDesc { 24 | return s.methodDesc 25 | } 26 | 27 | func (s *serverMethod) String() string { 28 | return s.methodDesc.MethodName 29 | } 30 | 31 | func (s *serverMethod) NewType() interface{} { 32 | if s.fieldType == nil { 33 | return nil 34 | } 35 | 36 | return reflect.New(s.fieldType).Interface() 37 | } 38 | 39 | func (s *serverMethod) Call(ctx context.Context, sess *session.Session, in interface{}) { 40 | s.methodDesc.Handler(s.srv, ctx, sess, in) 41 | } 42 | -------------------------------------------------------------------------------- /docs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metagogs/gogs/f1a44c5879cb63074d315a4fefbdb269267bcff7/docs/.gitkeep -------------------------------------------------------------------------------- /e2e/default.yaml: -------------------------------------------------------------------------------- 1 | Debug: true -------------------------------------------------------------------------------- /e2e/helper.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "io" 8 | "net/http" 9 | "testing" 10 | "time" 11 | 12 | "github.com/gorilla/websocket" 13 | "github.com/metagogs/gogs/config" 14 | "github.com/metagogs/gogs/e2e/testdata" 15 | "github.com/stretchr/testify/assert" 16 | "github.com/tidwall/gjson" 17 | ) 18 | 19 | func startServer(t *testing.T, config *config.Config) (context.CancelFunc, chan struct{}) { //nolint 20 | defer func() { 21 | if err := recover(); err != nil { 22 | t.Fatal("start error", err) 23 | } 24 | }() 25 | startTest := make(chan struct{}) 26 | config.StaredCallback = func() { 27 | <-time.After(1 * time.Second) 28 | startTest <- struct{}{} 29 | } 30 | t.Helper() 31 | ctx, cancel := context.WithCancel(context.Background()) 32 | go testdata.StartServer(ctx, config) 33 | return cancel, startTest 34 | } 35 | 36 | type TestClient struct { 37 | *websocket.Conn 38 | Datas chan []byte 39 | } 40 | 41 | func (c *TestClient) Start(t *testing.T) { 42 | t.Helper() 43 | for { 44 | _, data, err := c.ReadMessage() 45 | if err != nil { 46 | break 47 | } 48 | c.Datas <- data 49 | 50 | } 51 | } 52 | func (c *TestClient) Start2() { 53 | for { 54 | _, data, err := c.ReadMessage() 55 | if err != nil { 56 | break 57 | } 58 | c.Datas <- data 59 | 60 | } 61 | } 62 | 63 | func NewWSClinet(address string) (*TestClient, error) { //nolint 64 | conn, _, err := websocket.DefaultDialer.Dial(address, nil) //nolint 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | return &TestClient{ 70 | Conn: conn, 71 | Datas: make(chan []byte), 72 | }, nil 73 | 74 | } 75 | 76 | func runningInfo(t *testing.T) *gjson.Result { //nolint 77 | t.Helper() 78 | req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://127.0.0.1:9999/admin", nil) 79 | if err != nil { 80 | t.Fatal(err) 81 | } 82 | resp, err := http.DefaultClient.Do(req) 83 | if err != nil { 84 | t.Fatal(err) 85 | } 86 | defer resp.Body.Close() 87 | bytes, err := io.ReadAll(resp.Body) 88 | if err != nil { 89 | t.Fatal(err) 90 | } 91 | 92 | result := gjson.ParseBytes(bytes) 93 | return &result 94 | } 95 | 96 | func userLogin(t *testing.T, name string) string { //nolint 97 | t.Helper() 98 | data, _ := json.Marshal(map[string]string{ //nolint 99 | "username": name, 100 | "password": "e2epassword", 101 | }) 102 | bf := bytes.NewReader(data) 103 | req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "http://127.0.0.1:8890/user/login", bf) 104 | if err != nil { 105 | t.Fatal(err) 106 | } 107 | req.Header.Add("Content-Type", "application/json") 108 | resp, err := http.DefaultClient.Do(req) 109 | if err != nil { 110 | t.Fatal(err) 111 | } 112 | defer resp.Body.Close() 113 | bytes, err := io.ReadAll(resp.Body) 114 | if err != nil { 115 | t.Fatal(err) 116 | } 117 | 118 | result := gjson.ParseBytes(bytes) 119 | return result.Get("data").Get("uid").String() 120 | } 121 | 122 | func encodeMessage(t *testing.T, in interface{}) []byte { //nolint 123 | t.Helper() 124 | p, err := testdata.TestApp.MessageServer.EncodeMessage(in) 125 | assert.Nil(t, err) 126 | return p.ToData().B 127 | } 128 | -------------------------------------------------------------------------------- /e2e/smoke/config.yaml: -------------------------------------------------------------------------------- 1 | Debug: true -------------------------------------------------------------------------------- /e2e/smoke/data.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package model; 4 | option go_package = "/model"; 5 | 6 | // the Main Components 7 | // only have one, include some child componetns 8 | // all the message in this struct is the componetns 9 | // @gogs:Components 10 | message Components { 11 | // don't care the filed name, we never use it 12 | // but you should be careful about the filed number 13 | BaseWorld BaseWorld = 1; 14 | } 15 | 16 | // componetns, 17 | // all the messages are used for communication between the client and the server 18 | message BaseWorld { 19 | // don't care the filed name, we never use it 20 | // but you should be careful about the filed number 21 | BindUser BindUser = 1; 22 | 23 | BindSuccess BindSuccess = 2; 24 | } 25 | // common message 26 | // the message is used for the client and the server to communicate 27 | // the corresponding method will be generated according to this message 28 | message BindUser { 29 | string uid = 1; 30 | } 31 | 32 | 33 | // it is only used for messages sent from the server to the client 34 | // the server will not receive the message and will not generate the corresponding method 35 | // @gogs:ServerMessage 36 | message BindSuccess { 37 | } -------------------------------------------------------------------------------- /e2e/smoke/internal/logic/baseworld/bind_user_logic.go: -------------------------------------------------------------------------------- 1 | package baseworld 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/metagogs/gogs/e2e/smoke/internal/svc" 7 | "github.com/metagogs/gogs/e2e/smoke/model" 8 | "github.com/metagogs/gogs/gslog" 9 | "github.com/metagogs/gogs/session" 10 | "go.uber.org/zap" 11 | ) 12 | 13 | type BindUserLogic struct { 14 | ctx context.Context 15 | svcCtx *svc.ServiceContext 16 | session *session.Session 17 | *zap.Logger 18 | } 19 | 20 | func NewBindUserLogic(ctx context.Context, svcCtx *svc.ServiceContext, sess *session.Session) *BindUserLogic { 21 | return &BindUserLogic{ 22 | ctx: ctx, 23 | svcCtx: svcCtx, 24 | session: sess, 25 | Logger: gslog.NewLog("bind_user_logic"), 26 | } 27 | } 28 | 29 | func (l *BindUserLogic) Handler(in *model.BindUser) { 30 | 31 | } 32 | -------------------------------------------------------------------------------- /e2e/smoke/internal/message/message.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "github.com/metagogs/gogs/e2e/smoke/model" 5 | "github.com/metagogs/gogs/session" 6 | ) 7 | 8 | func SendBindUser(s *session.Session, in *model.BindUser) error { 9 | return s.SendMessage(in, "BindUser") 10 | } 11 | 12 | func SendBindSuccess(s *session.Session, in *model.BindSuccess) error { 13 | return s.SendMessage(in, "BindSuccess") 14 | } 15 | -------------------------------------------------------------------------------- /e2e/smoke/internal/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/metagogs/gogs/e2e/smoke/internal/logic/baseworld" 7 | "github.com/metagogs/gogs/e2e/smoke/model" 8 | 9 | "github.com/metagogs/gogs/e2e/smoke/internal/svc" 10 | "github.com/metagogs/gogs/session" 11 | ) 12 | 13 | type Server struct { 14 | svcCtx *svc.ServiceContext 15 | } 16 | 17 | func NewServer(svcCtx *svc.ServiceContext) *Server { 18 | return &Server{ 19 | svcCtx: svcCtx, 20 | } 21 | } 22 | 23 | func (gogs *Server) BindUser(ctx context.Context, s *session.Session, in *model.BindUser) { 24 | l := baseworld.NewBindUserLogic(ctx, gogs.svcCtx, s) 25 | l.Handler(in) 26 | } 27 | -------------------------------------------------------------------------------- /e2e/smoke/internal/svc/service_context.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "github.com/metagogs/gogs" 5 | ) 6 | 7 | type ServiceContext struct { 8 | *gogs.App 9 | } 10 | 11 | func NewServiceContext(app *gogs.App) *ServiceContext { 12 | return &ServiceContext{ 13 | App: app, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /e2e/smoke/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/metagogs/gogs" 5 | "github.com/metagogs/gogs/acceptor" 6 | "github.com/metagogs/gogs/config" 7 | "github.com/metagogs/gogs/e2e/smoke/internal/server" 8 | "github.com/metagogs/gogs/e2e/smoke/internal/svc" 9 | "github.com/metagogs/gogs/e2e/smoke/model" 10 | ) 11 | 12 | func main() { 13 | 14 | config := config.NewConfig() 15 | 16 | app := gogs.NewApp(config) 17 | app.AddAcceptor(acceptor.NewWSAcceptor(&acceptor.AcceptorConfig{ 18 | HttpPort: 8888, 19 | Name: "base", 20 | Groups: []*acceptor.AcceptorGroupConfig{ 21 | &acceptor.AcceptorGroupConfig{ 22 | GroupName: "base", 23 | }, 24 | }, 25 | })) 26 | 27 | app.AddAcceptor(acceptor.NewWebRTCAcceptor(&acceptor.AcceptorConfig{ 28 | HttpPort: 8889, 29 | UdpPort: 9000, 30 | Name: "world", 31 | Groups: []*acceptor.AcceptorGroupConfig{ 32 | &acceptor.AcceptorGroupConfig{ 33 | GroupName: "data", 34 | }, 35 | }, 36 | })) 37 | 38 | ctx := svc.NewServiceContext(app) 39 | srv := server.NewServer(ctx) 40 | 41 | model.RegisterAllComponents(app, srv) 42 | 43 | defer app.Shutdown() 44 | app.Start() 45 | } 46 | -------------------------------------------------------------------------------- /e2e/smoke/model/data.ep.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | 7 | "github.com/metagogs/gogs" 8 | "github.com/metagogs/gogs/component" 9 | "github.com/metagogs/gogs/packet" 10 | "github.com/metagogs/gogs/session" 11 | ) 12 | 13 | func RegisterAllComponents(s *gogs.App, srv Component) { 14 | registerBaseWorldComponent(s, srv) 15 | 16 | } 17 | 18 | func registerBaseWorldComponent(s *gogs.App, srv Component) { 19 | s.RegisterComponent(_BaseWorldComponentDesc, srv) 20 | } 21 | 22 | type Component interface { 23 | BindUser(ctx context.Context, s *session.Session, in *BindUser) 24 | } 25 | 26 | func _BaseWorldComponent_BindUser_Handler(srv interface{}, ctx context.Context, sess *session.Session, in interface{}) { 27 | srv.(Component).BindUser(ctx, sess, in.(*BindUser)) 28 | } 29 | 30 | var _BaseWorldComponentDesc = component.ComponentDesc{ 31 | ComponentName: "BaseWorldComponent", 32 | ComponentIndex: 1, // equal to module index 33 | ComponentType: (*Component)(nil), 34 | Methods: []component.ComponentMethodDesc{ 35 | { 36 | MethodIndex: packet.CreateAction(packet.ServicePacket, 1, 1), // 0x810001 8454145 37 | FieldType: reflect.TypeOf(BindUser{}), 38 | Handler: _BaseWorldComponent_BindUser_Handler, 39 | FieldHandler: func() interface{} { 40 | return new(BindUser) 41 | }, 42 | }, 43 | { 44 | MethodIndex: packet.CreateAction(packet.ServicePacket, 1, 2), // 0x810002 8454146 45 | FieldType: reflect.TypeOf(BindSuccess{}), 46 | Handler: nil, 47 | FieldHandler: func() interface{} { 48 | return new(BindSuccess) 49 | }, 50 | }, 51 | }, 52 | } 53 | -------------------------------------------------------------------------------- /e2e/testdata/fakeinternal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Config struct { 4 | } 5 | 6 | type MyApolloConfig struct { 7 | // example: CloudKey string `json:"CloudKey" apollo:"CloudKey"` 8 | } 9 | -------------------------------------------------------------------------------- /e2e/testdata/fakeinternal/logic/baseworld/bind_user_logic.go: -------------------------------------------------------------------------------- 1 | package baseworld 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/metagogs/gogs/e2e/testdata/fakeinternal/message" 8 | "github.com/metagogs/gogs/e2e/testdata/fakeinternal/svc" 9 | "github.com/metagogs/gogs/e2e/testdata/game" 10 | "github.com/metagogs/gogs/session" 11 | ) 12 | 13 | var BindUserHandler = make(chan *game.BindUser) 14 | 15 | type BindUserLogic struct { 16 | ctx context.Context 17 | svcCtx *svc.ServiceContext 18 | session *session.Session 19 | } 20 | 21 | func NewBindUserLogic(ctx context.Context, svcCtx *svc.ServiceContext, sess *session.Session) *BindUserLogic { 22 | return &BindUserLogic{ 23 | ctx: ctx, 24 | svcCtx: svcCtx, 25 | session: sess, 26 | } 27 | } 28 | 29 | func (l *BindUserLogic) Handler(in *game.BindUser) { 30 | BindUserHandler <- in 31 | if strings.HasPrefix(in.Uid, "test_") { 32 | l.svcCtx.PlayerManager.CreateUser(in.Uid, in.Uid) 33 | } 34 | player, ok := l.svcCtx.PlayerManager.GetPlayer(in.Uid) 35 | if ok { 36 | l.session.SetUID(in.Uid) 37 | l.session.GetData().Set("name", player.Name) 38 | player.AddSession(l.session.GetConnInfo().AcceptorName, l.session.GetConnInfo().AcceptorGroup, l.session.ID()) 39 | l.session.OnClose(func(id int64) { 40 | player.DeleteSession(l.session.ID()) 41 | }) 42 | 43 | _ = message.SendBindSuccess(l.session, &game.BindSuccess{}) 44 | 45 | } else { 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /e2e/testdata/fakeinternal/logic/baseworld/join_world_logic.go: -------------------------------------------------------------------------------- 1 | package baseworld 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | "github.com/metagogs/gogs" 8 | "github.com/metagogs/gogs/e2e/testdata/fakeinternal/message" 9 | "github.com/metagogs/gogs/e2e/testdata/fakeinternal/svc" 10 | "github.com/metagogs/gogs/e2e/testdata/game" 11 | "github.com/metagogs/gogs/session" 12 | ) 13 | 14 | var JoinWorldHandler = make(chan *game.JoinWorld) 15 | 16 | type JoinWorldLogic struct { 17 | ctx context.Context 18 | svcCtx *svc.ServiceContext 19 | session *session.Session 20 | } 21 | 22 | func NewJoinWorldLogic(ctx context.Context, svcCtx *svc.ServiceContext, sess *session.Session) *JoinWorldLogic { 23 | return &JoinWorldLogic{ 24 | ctx: ctx, 25 | svcCtx: svcCtx, 26 | session: sess, 27 | } 28 | } 29 | 30 | var beanPool = sync.Pool{ 31 | New: func() interface{} { 32 | return &game.JoinWorldNotify{} 33 | }, 34 | } 35 | 36 | func (l *JoinWorldLogic) Handler(in *game.JoinWorld) { 37 | JoinWorldHandler <- in 38 | if !l.session.IsLogin() { 39 | return 40 | } 41 | 42 | player, exist := l.svcCtx.PlayerManager.GetPlayer(l.session.UID()) 43 | if !exist { 44 | return 45 | } 46 | 47 | if err := l.svcCtx.World.AddUser(l.ctx, player.UID); err != nil { 48 | } 49 | player.OnExist(func() { 50 | _ = l.svcCtx.World.RemoveUser(l.ctx, player.UID) 51 | }) 52 | 53 | worldUids := l.svcCtx.World.GetUsers(l.ctx) 54 | 55 | _ = message.SendJoinWorldSuccess(l.session, &game.JoinWorldSuccess{ 56 | Uids: worldUids, 57 | }) 58 | 59 | sendMsg, _ := beanPool.Get().(*game.JoinWorldNotify) 60 | sendMsg.Uid = player.UID 61 | sendMsg.Name = player.Name 62 | defer beanPool.Put(sendMsg) 63 | 64 | uids := l.svcCtx.World.GetUsers(l.ctx) 65 | gogs.BroadcastMessage(uids, sendMsg, nil, l.session.UID()) 66 | } 67 | -------------------------------------------------------------------------------- /e2e/testdata/fakeinternal/logic/baseworld/update_user_in_world_logic.go: -------------------------------------------------------------------------------- 1 | package baseworld 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/metagogs/gogs" 7 | "github.com/metagogs/gogs/e2e/testdata/fakeinternal/svc" 8 | "github.com/metagogs/gogs/e2e/testdata/game" 9 | "github.com/metagogs/gogs/session" 10 | ) 11 | 12 | type UpdateUserInWorldLogic struct { 13 | ctx context.Context 14 | svcCtx *svc.ServiceContext 15 | session *session.Session 16 | } 17 | 18 | func NewUpdateUserInWorldLogic(ctx context.Context, svcCtx *svc.ServiceContext, sess *session.Session) *UpdateUserInWorldLogic { 19 | return &UpdateUserInWorldLogic{ 20 | ctx: ctx, 21 | svcCtx: svcCtx, 22 | session: sess, 23 | } 24 | } 25 | 26 | func (l *UpdateUserInWorldLogic) Handler(in *game.UpdateUserInWorld) { 27 | if !l.session.IsLogin() { 28 | return 29 | } 30 | 31 | player, exist := l.svcCtx.PlayerManager.GetPlayer(l.session.UID()) 32 | if !exist { 33 | return 34 | } 35 | //make sure uid is right 36 | in.Uid = player.UID 37 | uids := l.svcCtx.World.GetUsers(l.ctx) 38 | 39 | // broadcast to users with same message, encode the message in here to save time 40 | // it avoid encode the message for every user 41 | _ = gogs.BroadcastMessage(uids, in, nil, l.session.UID()) 42 | } 43 | -------------------------------------------------------------------------------- /e2e/testdata/fakeinternal/logic/web/user_login_logic.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/metagogs/gogs/e2e/testdata/fakeinternal/svc" 9 | ) 10 | 11 | type UserLoginLogic struct { 12 | ctx context.Context 13 | svcCtx *svc.ServiceContext 14 | } 15 | 16 | func NewUserLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserLoginLogic { 17 | return &UserLoginLogic{ 18 | ctx: ctx, 19 | svcCtx: svcCtx, 20 | } 21 | } 22 | 23 | type UserLoginRequest struct { 24 | Username string `json:"username"` 25 | Password string `json:"password"` 26 | } 27 | 28 | type UserLoginResponse struct { 29 | UID string `json:"uid"` 30 | } 31 | 32 | func (l *UserLoginLogic) Handler(c *gin.Context) { 33 | var msg UserLoginRequest 34 | if err := c.ShouldBindJSON(&msg); err != nil { 35 | c.JSON(http.StatusOK, gin.H{ 36 | "code": 500, 37 | "msg": err.Error(), 38 | }) 39 | return 40 | } 41 | 42 | if len(msg.Username) == 0 { 43 | c.JSON(http.StatusOK, gin.H{ 44 | "code": 500, 45 | "msg": "username is empty", 46 | }) 47 | return 48 | } 49 | 50 | response := UserLoginResponse{ 51 | UID: l.svcCtx.SF.Generate().String(), 52 | } 53 | 54 | l.svcCtx.PlayerManager.CreateUser(response.UID, msg.Username) 55 | 56 | c.JSON(http.StatusOK, gin.H{ 57 | "code": 200, 58 | "data": response, 59 | }) 60 | 61 | } 62 | -------------------------------------------------------------------------------- /e2e/testdata/fakeinternal/message/message.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "github.com/metagogs/gogs/e2e/testdata/game" 5 | "github.com/metagogs/gogs/session" 6 | ) 7 | 8 | func SendBindUser(s *session.Session, in *game.BindUser) error { 9 | return s.SendMessage(in, "BindUser") 10 | } 11 | 12 | func SendBindSuccess(s *session.Session, in *game.BindSuccess) error { 13 | return s.SendMessage(in, "BindSuccess") 14 | } 15 | 16 | func SendJoinWorld(s *session.Session, in *game.JoinWorld) error { 17 | return s.SendMessage(in, "JoinWorld") 18 | } 19 | 20 | func SendJoinWorldSuccess(s *session.Session, in *game.JoinWorldSuccess) error { 21 | return s.SendMessage(in, "JoinWorldSuccess") 22 | } 23 | 24 | func SendJoinWorldNotify(s *session.Session, in *game.JoinWorldNotify) error { 25 | return s.SendMessage(in, "JoinWorldNotify") 26 | } 27 | 28 | func SendUpdateUserInWorld(s *session.Session, in *game.UpdateUserInWorld) error { 29 | return s.SendMessage(in, "UpdateUserInWorld") 30 | } 31 | -------------------------------------------------------------------------------- /e2e/testdata/fakeinternal/player/manager.go: -------------------------------------------------------------------------------- 1 | package player 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type PlayerManager struct { 8 | players sync.Map 9 | } 10 | 11 | func NewPlayerManager() *PlayerManager { 12 | return &PlayerManager{} 13 | } 14 | 15 | func (p *PlayerManager) CreateUser(uid, name string) { 16 | player := newPlayer(uid, name, p) 17 | p.players.Store(uid, player) 18 | } 19 | 20 | func (p *PlayerManager) GetPlayer(uid string) (*Player, bool) { 21 | v, ok := p.players.Load(uid) 22 | if !ok { 23 | return nil, false 24 | } 25 | 26 | return v.(*Player), true 27 | } 28 | 29 | func (p *PlayerManager) DeletePlayer(uid string) { 30 | p.players.Delete(uid) 31 | } 32 | -------------------------------------------------------------------------------- /e2e/testdata/fakeinternal/player/palyer.go: -------------------------------------------------------------------------------- 1 | package player 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type Player struct { 8 | UID string 9 | Name string 10 | Sessions sync.Map 11 | SessionID sync.Map 12 | playerManager *PlayerManager 13 | OnCloseCallbacks []func() 14 | } 15 | 16 | func newPlayer(uid string, name string, playerManager *PlayerManager) *Player { 17 | return &Player{ 18 | UID: uid, 19 | Name: name, 20 | playerManager: playerManager, 21 | } 22 | } 23 | 24 | func (player *Player) AddSession(name, group string, id int64) { 25 | player.Sessions.Store(name+group, id) 26 | player.SessionID.Store(id, name+group) 27 | } 28 | 29 | func (player *Player) GetSession(name, group string) (int64, bool) { 30 | v, ok := player.Sessions.Load(name + group) 31 | if !ok { 32 | return 0, false 33 | } 34 | 35 | return v.(int64), true 36 | } 37 | 38 | func (player *Player) DeleteSession(id int64) { 39 | if v, ok := player.SessionID.Load(id); ok { 40 | player.Sessions.Delete(v.(string)) 41 | player.SessionID.Delete(id) 42 | } 43 | count := 0 44 | player.SessionID.Range(func(key, value interface{}) bool { 45 | count++ 46 | return true 47 | }) 48 | if count == 0 { 49 | player.Level() 50 | } 51 | } 52 | 53 | func (player *Player) Level() { 54 | player.playerManager.DeletePlayer(player.UID) 55 | for _, fn := range player.OnCloseCallbacks { 56 | fn() 57 | } 58 | } 59 | 60 | func (player *Player) OnExist(f func()) { 61 | player.OnCloseCallbacks = append(player.OnCloseCallbacks, f) 62 | } 63 | -------------------------------------------------------------------------------- /e2e/testdata/fakeinternal/server/game_server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/metagogs/gogs/e2e/testdata/fakeinternal/logic/baseworld" 7 | "github.com/metagogs/gogs/e2e/testdata/game" 8 | 9 | "github.com/metagogs/gogs/e2e/testdata/fakeinternal/svc" 10 | "github.com/metagogs/gogs/session" 11 | ) 12 | 13 | type Server struct { 14 | svcCtx *svc.ServiceContext 15 | } 16 | 17 | func NewServer(svcCtx *svc.ServiceContext) *Server { 18 | return &Server{ 19 | svcCtx: svcCtx, 20 | } 21 | } 22 | 23 | func (gogs *Server) BindUser(ctx context.Context, s *session.Session, in *game.BindUser) { 24 | l := baseworld.NewBindUserLogic(ctx, gogs.svcCtx, s) 25 | l.Handler(in) 26 | } 27 | 28 | func (gogs *Server) JoinWorld(ctx context.Context, s *session.Session, in *game.JoinWorld) { 29 | l := baseworld.NewJoinWorldLogic(ctx, gogs.svcCtx, s) 30 | l.Handler(in) 31 | } 32 | 33 | func (gogs *Server) UpdateUserInWorld(ctx context.Context, s *session.Session, in *game.UpdateUserInWorld) { 34 | l := baseworld.NewUpdateUserInWorldLogic(ctx, gogs.svcCtx, s) 35 | l.Handler(in) 36 | } 37 | -------------------------------------------------------------------------------- /e2e/testdata/fakeinternal/server/web_server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gin-contrib/cors" 7 | "github.com/gin-gonic/gin" 8 | "github.com/metagogs/gogs/e2e/testdata/fakeinternal/logic/web" 9 | "github.com/metagogs/gogs/e2e/testdata/fakeinternal/svc" 10 | ) 11 | 12 | type WebServer struct { 13 | svcCtx *svc.ServiceContext 14 | engine *gin.Engine 15 | } 16 | 17 | func NewWebServer(svcCtx *svc.ServiceContext) *WebServer { 18 | return &WebServer{ 19 | svcCtx: svcCtx, 20 | } 21 | } 22 | 23 | func (s *WebServer) RegisterHandler(engine *gin.Engine) { 24 | engine.Use(cors.New(cors.Config{ 25 | AllowOrigins: []string{"*"}, 26 | AllowMethods: []string{"GET", "POST", "PUT", "PATCH"}, 27 | AllowHeaders: []string{"*"}, 28 | ExposeHeaders: []string{"Content-Length"}, 29 | AllowCredentials: true, 30 | AllowOriginFunc: func(origin string) bool { 31 | return true 32 | }, 33 | })) 34 | engine.POST("/user/login", s.UserLogin) 35 | } 36 | 37 | func (s *WebServer) UserLogin(c *gin.Context) { 38 | web.NewUserLoginLogic(context.Background(), s.svcCtx).Handler(c) 39 | } 40 | -------------------------------------------------------------------------------- /e2e/testdata/fakeinternal/svc/service_context.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/bwmarrin/snowflake" 8 | "github.com/metagogs/gogs" 9 | "github.com/metagogs/gogs/e2e/testdata/fakeinternal/player" 10 | "github.com/metagogs/gogs/group" 11 | "github.com/metagogs/gogs/utils/snow" 12 | ) 13 | 14 | type ServiceContext struct { 15 | *gogs.App 16 | SF *snowflake.Node 17 | PlayerManager *player.PlayerManager 18 | World *group.MemoryGroup 19 | } 20 | 21 | func NewServiceContext(app *gogs.App) *ServiceContext { 22 | sf, err := snow.NewSnowNode() 23 | if err != nil { 24 | fmt.Println(err.Error()) 25 | os.Exit(1) 26 | } 27 | 28 | pl := player.NewPlayerManager() 29 | //test 30 | pl.CreateUser("123", "neosu") 31 | pl.CreateUser("456", "szp") 32 | 33 | world := group.NewMemoryGroup("world", sf.Generate().Int64()) 34 | 35 | return &ServiceContext{ 36 | App: app, 37 | SF: sf, 38 | PlayerManager: pl, 39 | World: world, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /e2e/testdata/game.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package game; 4 | option go_package = "/game"; 5 | 6 | 7 | // @gogs:Components 8 | message Components { 9 | BaseWorld BaseWorld = 1; 10 | } 11 | 12 | message BaseWorld { 13 | BindUser BindUser = 1; 14 | JoinWorld JoinWorld = 2; 15 | JoinWorldNotify JoinWorldNotify = 3; 16 | UpdateUserInWorld UpdateUserInWorld = 4; 17 | BindSuccess BindSuccess = 5; 18 | JoinWorldSuccess JoinWorldSuccess =6; 19 | } 20 | 21 | message Vecotr3 { 22 | float x = 1; 23 | float y = 2; 24 | float z = 3; 25 | } 26 | 27 | message BindUser { 28 | string uid = 1; 29 | } 30 | 31 | // @gogs:ServerMessage 32 | message BindSuccess { 33 | } 34 | 35 | message JoinWorld { 36 | string uid = 1; 37 | } 38 | 39 | // @gogs:ServerMessage 40 | message JoinWorldSuccess { 41 | repeated string uids = 1; 42 | } 43 | 44 | // @gogs:ServerMessage 45 | message JoinWorldNotify { 46 | string uid = 1; 47 | 48 | string name = 2; 49 | } 50 | 51 | message UpdateUserInWorld { 52 | string uid = 1; 53 | 54 | Vecotr3 position = 2; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /e2e/testdata/game/game.ep.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | 7 | "github.com/metagogs/gogs" 8 | "github.com/metagogs/gogs/component" 9 | "github.com/metagogs/gogs/packet" 10 | "github.com/metagogs/gogs/session" 11 | ) 12 | 13 | func RegisterAllComponents(s *gogs.App, srv Component) { 14 | registerBaseWorldComponent(s, srv) 15 | 16 | } 17 | 18 | func registerBaseWorldComponent(s *gogs.App, srv Component) { 19 | s.RegisterComponent(_BaseWorldComponentDesc, srv) 20 | } 21 | 22 | type Component interface { 23 | BindUser(ctx context.Context, s *session.Session, in *BindUser) 24 | 25 | JoinWorld(ctx context.Context, s *session.Session, in *JoinWorld) 26 | 27 | UpdateUserInWorld(ctx context.Context, s *session.Session, in *UpdateUserInWorld) 28 | } 29 | 30 | func _BaseWorldComponent_BindUser_Handler(srv interface{}, ctx context.Context, sess *session.Session, in interface{}) { 31 | srv.(Component).BindUser(ctx, sess, in.(*BindUser)) 32 | } 33 | 34 | func _BaseWorldComponent_JoinWorld_Handler(srv interface{}, ctx context.Context, sess *session.Session, in interface{}) { 35 | srv.(Component).JoinWorld(ctx, sess, in.(*JoinWorld)) 36 | } 37 | 38 | func _BaseWorldComponent_UpdateUserInWorld_Handler(srv interface{}, ctx context.Context, sess *session.Session, in interface{}) { 39 | srv.(Component).UpdateUserInWorld(ctx, sess, in.(*UpdateUserInWorld)) 40 | } 41 | 42 | var _BaseWorldComponentDesc = component.ComponentDesc{ 43 | ComponentName: "BaseWorldComponent", 44 | ComponentIndex: 1, // equal to module index 45 | ComponentType: (*Component)(nil), 46 | Methods: []component.ComponentMethodDesc{ 47 | { 48 | MethodIndex: packet.CreateAction(packet.ServicePacket, 1, 1), 49 | FieldType: reflect.TypeOf(BindUser{}), 50 | Handler: _BaseWorldComponent_BindUser_Handler, 51 | FieldHandler: func() interface{} { 52 | return new(BindUser) 53 | }, 54 | }, 55 | { 56 | MethodIndex: packet.CreateAction(packet.ServicePacket, 1, 2), 57 | FieldType: reflect.TypeOf(JoinWorld{}), 58 | Handler: _BaseWorldComponent_JoinWorld_Handler, 59 | FieldHandler: func() interface{} { 60 | return new(JoinWorld) 61 | }, 62 | }, 63 | { 64 | MethodIndex: packet.CreateAction(packet.ServicePacket, 1, 3), 65 | FieldType: reflect.TypeOf(JoinWorldNotify{}), 66 | Handler: nil, 67 | FieldHandler: func() interface{} { 68 | return new(JoinWorldNotify) 69 | }, 70 | }, 71 | { 72 | MethodIndex: packet.CreateAction(packet.ServicePacket, 1, 4), 73 | FieldType: reflect.TypeOf(UpdateUserInWorld{}), 74 | Handler: _BaseWorldComponent_UpdateUserInWorld_Handler, 75 | FieldHandler: func() interface{} { 76 | return new(UpdateUserInWorld) 77 | }, 78 | }, 79 | { 80 | MethodIndex: packet.CreateAction(packet.ServicePacket, 1, 5), 81 | FieldType: reflect.TypeOf(BindSuccess{}), 82 | Handler: nil, 83 | FieldHandler: func() interface{} { 84 | return new(BindSuccess) 85 | }, 86 | }, 87 | { 88 | MethodIndex: packet.CreateAction(packet.ServicePacket, 1, 6), 89 | FieldType: reflect.TypeOf(JoinWorldSuccess{}), 90 | Handler: nil, 91 | FieldHandler: func() interface{} { 92 | return new(JoinWorldSuccess) 93 | }, 94 | }, 95 | }, 96 | } 97 | -------------------------------------------------------------------------------- /e2e/testdata/main.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/metagogs/gogs" 8 | "github.com/metagogs/gogs/acceptor" 9 | "github.com/metagogs/gogs/config" 10 | "github.com/metagogs/gogs/e2e/testdata/fakeinternal/server" 11 | "github.com/metagogs/gogs/e2e/testdata/fakeinternal/svc" 12 | "github.com/metagogs/gogs/e2e/testdata/game" 13 | ) 14 | 15 | var TestApp *gogs.App 16 | 17 | func StartServer(closeCtx context.Context, config *config.Config) { 18 | 19 | TestApp = gogs.NewApp(config) 20 | 21 | go func() { 22 | <-closeCtx.Done() 23 | TestApp.Shutdown() 24 | }() 25 | 26 | TestApp.AddAcceptor(acceptor.NewWSAcceptor(&acceptor.AcceptorConfig{ 27 | HttpPort: 8888, 28 | Name: "base", 29 | Groups: []*acceptor.AcceptorGroupConfig{ 30 | &acceptor.AcceptorGroupConfig{ 31 | GroupName: "base", 32 | BucketFillInterval: 40 * time.Millisecond, 33 | BucketCapacity: 10, 34 | }, 35 | }, 36 | })) 37 | 38 | TestApp.AddAcceptor(acceptor.NewWebRTCAcceptor(&acceptor.AcceptorConfig{ 39 | HttpPort: 8889, 40 | UdpPort: 9001, 41 | Name: "world", 42 | Groups: []*acceptor.AcceptorGroupConfig{ 43 | &acceptor.AcceptorGroupConfig{ 44 | GroupName: "data", 45 | BucketFillInterval: 40 * time.Millisecond, 46 | BucketCapacity: 10, 47 | }, 48 | }, 49 | })) 50 | 51 | ctx := svc.NewServiceContext(TestApp) 52 | gameSrv := server.NewServer(ctx) 53 | game.RegisterAllComponents(TestApp, gameSrv) 54 | 55 | httpSrv := server.NewWebServer(ctx) 56 | TestApp.RegisterWebHandler(8890, httpSrv.RegisterHandler) 57 | 58 | defer TestApp.Shutdown() 59 | TestApp.Start() 60 | 61 | // for testing 62 | for _, acc := range TestApp.GetAcceptors() { 63 | acc.Stop() 64 | } 65 | 66 | TestApp.Info("test server is shutdown") 67 | } 68 | -------------------------------------------------------------------------------- /e2e/testdata/run/config.yaml: -------------------------------------------------------------------------------- 1 | Debug: true -------------------------------------------------------------------------------- /e2e/testdata/run/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/metagogs/gogs/config" 7 | "github.com/metagogs/gogs/e2e/testdata" 8 | ) 9 | 10 | func main() { 11 | config := config.NewConfig() 12 | testdata.StartServer(context.Background(), config) 13 | } 14 | -------------------------------------------------------------------------------- /global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | var ( 4 | GoGSDebug = false // debug mode 5 | GOGS_DISABLE_LOG = false // disable all the log exclude the FatalLevel log 6 | ) 7 | -------------------------------------------------------------------------------- /group/constants.go: -------------------------------------------------------------------------------- 1 | package group 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrUserNotIntGroup = errors.New("user not in group") 7 | ErrUserExistInGroup = errors.New("user exist in group") 8 | ) 9 | -------------------------------------------------------------------------------- /group/group.go: -------------------------------------------------------------------------------- 1 | package group 2 | 3 | import "context" 4 | 5 | type Group interface { 6 | AddUser(ctx context.Context, uid string) error // add user to group 7 | RemoveUser(ctx context.Context, uid string) error // remove user from group 8 | RemoveUsers(ctx context.Context, uids []string) // remove users from group 9 | RemoveAllUsers(ctx context.Context) // remove all users from group 10 | GetUsers(ctx context.Context) []string // get all users in group 11 | GetUserCount(ctx context.Context) int // get user count in group 12 | GetLastRefresh(ctx context.Context) int64 // get last refresh time 13 | ContainsUser(ctx context.Context, uid string) bool // check if user is in group 14 | GetGroupName(ctx context.Context) string // get group name 15 | GetGroupID(ctx context.Context) int64 // get group id 16 | } 17 | -------------------------------------------------------------------------------- /group/group_service.go: -------------------------------------------------------------------------------- 1 | package group 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "sync" 8 | 9 | "github.com/bwmarrin/snowflake" 10 | "github.com/metagogs/gogs/utils/snow" 11 | ) 12 | 13 | type GroupServer struct { 14 | rooms sync.Map 15 | sf *snowflake.Node 16 | } 17 | 18 | func NewGroupServer() *GroupServer { 19 | sf, err := snow.NewSnowNode() 20 | if err != nil { 21 | fmt.Println(err.Error()) 22 | os.Exit(1) 23 | } 24 | return &GroupServer{ 25 | sf: sf, 26 | } 27 | } 28 | 29 | func (gs *GroupServer) CreateMemoryGroup(name string) Group { 30 | if group, ok := gs.rooms.Load(name); ok { 31 | return group.(*MemoryGroup) 32 | } 33 | 34 | group := NewMemoryGroup(name, gs.sf.Generate().Int64()) 35 | gs.rooms.Store(name, group) 36 | 37 | return group 38 | } 39 | 40 | func (gs *GroupServer) GetGroup(name string) (Group, bool) { 41 | if group, ok := gs.rooms.Load(name); ok { 42 | return group.(Group), true 43 | } 44 | 45 | return nil, false 46 | } 47 | 48 | func (gs *GroupServer) DeleteGroup(name string) { 49 | gs.rooms.Delete(name) 50 | } 51 | 52 | func (gs *GroupServer) DeleteUserByName(uid string) { 53 | gs.rooms.Range(func(key, value interface{}) bool { 54 | if group, ok := value.(Group); ok { 55 | _ = group.RemoveUser(context.TODO(), uid) 56 | } 57 | return true 58 | }) 59 | } 60 | 61 | func (gs *GroupServer) ListGroup() []Group { 62 | list := []Group{} 63 | gs.rooms.Range(func(key, value any) bool { 64 | list = append(list, value.(Group)) 65 | return true 66 | }) 67 | 68 | return list 69 | } 70 | -------------------------------------------------------------------------------- /group/group_service_test.go: -------------------------------------------------------------------------------- 1 | package group 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestGroupServer_CreateMemoryGroup(t *testing.T) { 10 | groupServer := NewGroupServer() 11 | 12 | groupServer.CreateMemoryGroup("test") 13 | groupServer.CreateMemoryGroup("test") 14 | g, exist := groupServer.GetGroup("test") 15 | assert.True(t, exist) 16 | assert.NotNil(t, g) 17 | 18 | g, exist = groupServer.GetGroup("test2") 19 | assert.False(t, exist) 20 | assert.Nil(t, g) 21 | 22 | groupServer.DeleteGroup("test") 23 | g, exist = groupServer.GetGroup("test") 24 | assert.False(t, exist) 25 | assert.Nil(t, g) 26 | 27 | groupServer.CreateMemoryGroup("test") 28 | g, exist = groupServer.GetGroup("test") 29 | assert.True(t, exist) 30 | assert.NotNil(t, g) 31 | 32 | err := g.AddUser(nil, "user1") 33 | assert.Nil(t, err) 34 | 35 | err = g.AddUser(nil, "user2") 36 | assert.Nil(t, err) 37 | 38 | groupServer.CreateMemoryGroup("test2") 39 | g2, exist := groupServer.GetGroup("test2") 40 | assert.True(t, exist) 41 | assert.NotNil(t, g2) 42 | 43 | err = g2.AddUser(nil, "user1") 44 | assert.Nil(t, err) 45 | 46 | groupServer.DeleteUserByName("user1") 47 | exist = g.ContainsUser(nil, "user1") 48 | assert.False(t, exist) 49 | 50 | exist = g2.ContainsUser(nil, "user1") 51 | assert.False(t, exist) 52 | 53 | exist = g.ContainsUser(nil, "user2") 54 | assert.True(t, exist) 55 | 56 | exist = g2.ContainsUser(nil, "user2") 57 | assert.False(t, exist) 58 | } 59 | -------------------------------------------------------------------------------- /group/memory_group.go: -------------------------------------------------------------------------------- 1 | package group 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | 8 | "github.com/metagogs/gogs/utils/slicex" 9 | ) 10 | 11 | var _ Group = (*MemoryGroup)(nil) 12 | 13 | type MemoryGroup struct { 14 | mutex sync.RWMutex 15 | name string 16 | uids map[string]struct{} 17 | uidsList []string 18 | groupID int64 19 | lastRefresh int64 20 | } 21 | 22 | func NewMemoryGroup(name string, groupID int64) *MemoryGroup { 23 | return &MemoryGroup{ 24 | name: name, 25 | groupID: groupID, 26 | uids: make(map[string]struct{}), 27 | lastRefresh: time.Now().Unix(), 28 | } 29 | } 30 | 31 | func (group *MemoryGroup) AddUser(ctx context.Context, uid string) error { 32 | group.mutex.Lock() 33 | defer group.mutex.Unlock() 34 | if _, ok := group.uids[uid]; !ok { 35 | group.uids[uid] = struct{}{} 36 | group.uidsList = append(group.uidsList, uid) 37 | group.lastRefresh = time.Now().Unix() 38 | return nil 39 | } 40 | 41 | return ErrUserExistInGroup 42 | } 43 | 44 | func (group *MemoryGroup) RemoveUser(ctx context.Context, uid string) error { 45 | group.mutex.Lock() 46 | defer group.mutex.Unlock() 47 | if _, ok := group.uids[uid]; ok { 48 | delete(group.uids, uid) 49 | group.uidsList = slicex.RemoveSliceItem(group.uidsList, uid) 50 | group.lastRefresh = time.Now().Unix() 51 | return nil 52 | } 53 | 54 | return ErrUserNotIntGroup 55 | } 56 | 57 | func (group *MemoryGroup) RemoveUsers(ctx context.Context, uids []string) { 58 | for _, uid := range uids { 59 | _ = group.RemoveUser(ctx, uid) 60 | group.lastRefresh = time.Now().Unix() 61 | } 62 | } 63 | 64 | func (group *MemoryGroup) RemoveAllUsers(ctx context.Context) { 65 | group.mutex.Lock() 66 | defer group.mutex.Unlock() 67 | group.uids = make(map[string]struct{}) 68 | group.uidsList = nil 69 | } 70 | 71 | func (group *MemoryGroup) GetUsers(ctx context.Context) []string { 72 | group.mutex.RLock() 73 | defer group.mutex.RUnlock() 74 | return group.uidsList 75 | } 76 | 77 | func (group *MemoryGroup) GetUserCount(ctx context.Context) int { 78 | return len(group.uidsList) 79 | } 80 | 81 | func (group *MemoryGroup) GetLastRefresh(ctx context.Context) int64 { 82 | return group.lastRefresh 83 | } 84 | 85 | func (group *MemoryGroup) ContainsUser(ctx context.Context, uid string) bool { 86 | group.mutex.RLock() 87 | defer group.mutex.RUnlock() 88 | _, ok := group.uids[uid] 89 | return ok 90 | } 91 | 92 | func (group *MemoryGroup) GetGroupName(ctx context.Context) string { 93 | return group.name 94 | } 95 | 96 | func (group *MemoryGroup) GetGroupID(ctx context.Context) int64 { 97 | return group.groupID 98 | } 99 | -------------------------------------------------------------------------------- /group/memory_group_test.go: -------------------------------------------------------------------------------- 1 | package group 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | 7 | "github.com/metagogs/gogs/utils/randstr" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestMemoryGroup_AddUser(t *testing.T) { 12 | memGroup := NewMemoryGroup("name", 1) 13 | id := memGroup.GetGroupID(nil) 14 | assert.Equal(t, int64(1), id) 15 | name := memGroup.GetGroupName(nil) 16 | assert.Equal(t, "name", name) 17 | 18 | err := memGroup.AddUser(nil, "test") 19 | assert.Nil(t, err) 20 | 21 | err = memGroup.AddUser(nil, "test") 22 | assert.Equal(t, ErrUserExistInGroup, err) 23 | 24 | err = memGroup.AddUser(nil, "test2") 25 | assert.Nil(t, err) 26 | 27 | uids := memGroup.GetUsers(nil) 28 | assert.Equal(t, 2, len(uids)) 29 | assert.Contains(t, uids, "test") 30 | assert.Contains(t, uids, "test2") 31 | 32 | count := memGroup.GetUserCount(nil) 33 | assert.Equal(t, 2, count) 34 | 35 | err = memGroup.RemoveUser(nil, "test") 36 | assert.Nil(t, err) 37 | 38 | uids = memGroup.GetUsers(nil) 39 | assert.Equal(t, 1, len(uids)) 40 | assert.Contains(t, uids, "test2") 41 | 42 | memGroup.RemoveAllUsers(nil) 43 | uids = memGroup.GetUsers(nil) 44 | assert.Equal(t, 0, len(uids)) 45 | 46 | count = memGroup.GetUserCount(nil) 47 | assert.Equal(t, 0, count) 48 | 49 | err = memGroup.RemoveUser(nil, "test") 50 | assert.Equal(t, ErrUserNotIntGroup, err) 51 | 52 | err = memGroup.AddUser(nil, "test") 53 | assert.Nil(t, err) 54 | 55 | err = memGroup.AddUser(nil, "test2") 56 | assert.Nil(t, err) 57 | 58 | memGroup.RemoveUsers(nil, []string{"test"}) 59 | uids = memGroup.GetUsers(nil) 60 | assert.Equal(t, 1, len(uids)) 61 | assert.Contains(t, uids, "test2") 62 | 63 | exist := memGroup.ContainsUser(nil, "test") 64 | assert.False(t, exist) 65 | 66 | exist = memGroup.ContainsUser(nil, "test2") 67 | assert.True(t, exist) 68 | 69 | var wg sync.WaitGroup 70 | for i := 0; i < 1000; i++ { 71 | wg.Add(1) 72 | go func() { 73 | defer wg.Done() 74 | memGroup.AddUser(nil, randstr.RandStr(10)) 75 | memGroup.AddUser(nil, "test") 76 | memGroup.GetUsers(nil) 77 | memGroup.GetUserCount(nil) 78 | memGroup.GetGroupID(nil) 79 | memGroup.GetGroupName(nil) 80 | memGroup.ContainsUser(nil, "test") 81 | memGroup.ContainsUser(nil, "test2") 82 | memGroup.RemoveUser(nil, "test") 83 | memGroup.RemoveUsers(nil, []string{"test2"}) 84 | memGroup.RemoveAllUsers(nil) 85 | memGroup.AddUser(nil, randstr.RandStr(10)) 86 | memGroup.AddUser(nil, "test") 87 | memGroup.GetUsers(nil) 88 | memGroup.GetUserCount(nil) 89 | memGroup.RemoveAllUsers(nil) 90 | }() 91 | } 92 | wg.Wait() 93 | } 94 | -------------------------------------------------------------------------------- /gslog/gslog.go: -------------------------------------------------------------------------------- 1 | package gslog 2 | 3 | import ( 4 | "github.com/metagogs/gogs/global" 5 | "go.uber.org/zap" 6 | ) 7 | 8 | var devLog = zap.Config{ 9 | Level: zap.NewAtomicLevelAt(zap.DebugLevel), 10 | Development: true, 11 | Encoding: "console", 12 | EncoderConfig: zap.NewDevelopmentEncoderConfig(), 13 | OutputPaths: []string{"stderr"}, 14 | ErrorOutputPaths: []string{"stderr"}, 15 | } 16 | 17 | var prodLog = zap.Config{ 18 | Level: zap.NewAtomicLevelAt(zap.InfoLevel), 19 | Development: false, 20 | Sampling: nil, 21 | Encoding: "json", 22 | EncoderConfig: zap.NewProductionEncoderConfig(), 23 | OutputPaths: []string{"stderr"}, 24 | ErrorOutputPaths: []string{"stderr"}, 25 | } 26 | 27 | var disableLog = zap.Config{ 28 | Level: zap.NewAtomicLevelAt(zap.FatalLevel), 29 | Development: false, 30 | Sampling: nil, 31 | Encoding: "json", 32 | EncoderConfig: zap.NewProductionEncoderConfig(), 33 | OutputPaths: []string{"stderr"}, 34 | ErrorOutputPaths: []string{"stderr"}, 35 | } 36 | 37 | func NewLog(name string) *zap.Logger { 38 | if global.GOGS_DISABLE_LOG { 39 | // only show the fatal log 40 | logger, _ := disableLog.Build() 41 | return logger.Named("gogs_" + name) 42 | } 43 | 44 | if global.GoGSDebug { 45 | logger, _ := devLog.Build() 46 | return logger.Named("gogs_" + name) 47 | } 48 | 49 | logger, _ := prodLog.Build() 50 | 51 | return logger.Named("gogs_" + name) 52 | } 53 | -------------------------------------------------------------------------------- /handler/handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "context" 7 | "errors" 8 | "math" 9 | "runtime/debug" 10 | 11 | "github.com/metagogs/gogs/acceptor" 12 | "github.com/metagogs/gogs/agent" 13 | "github.com/metagogs/gogs/config" 14 | "github.com/metagogs/gogs/gslog" 15 | "github.com/metagogs/gogs/message" 16 | "github.com/metagogs/gogs/packet" 17 | "github.com/metagogs/gogs/session" 18 | "go.uber.org/zap" 19 | ) 20 | 21 | type unHandlerMessage struct { 22 | ctx context.Context 23 | sess *session.Session 24 | in *packet.Packet 25 | } 26 | 27 | type HandlerService struct { 28 | config *config.Config 29 | agentFactory *agent.AgentFactory 30 | messageServer *message.MessageServer 31 | chLocalMessageProcess chan unHandlerMessage 32 | *zap.Logger 33 | } 34 | 35 | // NewHandlerService create a new handler service, can get all the client's message 36 | func NewHandlerService(config *config.Config, factory *agent.AgentFactory, messageServer *message.MessageServer) *HandlerService { 37 | return &HandlerService{ 38 | config: config, 39 | agentFactory: factory, 40 | Logger: gslog.NewLog("handler"), 41 | messageServer: messageServer, 42 | chLocalMessageProcess: make(chan unHandlerMessage, 100), 43 | } 44 | } 45 | 46 | func (h *HandlerService) SetAgentFactory(factory *agent.AgentFactory) { 47 | h.agentFactory = factory 48 | } 49 | 50 | func (h *HandlerService) dispatchMessage(m unHandlerMessage) { 51 | defer func() { 52 | if r := recover(); r != nil { 53 | h.toPanicError(r) 54 | h.Error("dispatch message error", zap.Any("recover", r)) 55 | } 56 | }() 57 | if err := h.messageServer.CallMessageHandler(m.ctx, m.sess, m.in); err != nil { 58 | h.Error("dispatch message error", 59 | zap.Error(err), 60 | zap.Int64("agent_id", m.sess.ID())) 61 | } 62 | } 63 | 64 | func (h *HandlerService) toPanicError(r interface{}) { 65 | buf := bytes.Buffer{} 66 | stackScanner := bufio.NewScanner(bytes.NewReader(debug.Stack())) 67 | for i := 0; i < math.MaxInt32; i++ { 68 | if !stackScanner.Scan() { 69 | break 70 | } 71 | 72 | text := stackScanner.Text() 73 | buf.WriteString(text + "\n") 74 | } 75 | h.Error(buf.String()) 76 | } 77 | 78 | func (h *HandlerService) Handle(conn acceptor.AcceptorConn) { 79 | a := h.agentFactory.NewAgent(conn) 80 | h.Info("get new agent connection in handle", zap.Int64("agent_id", a.AgentID)) 81 | 82 | go a.Start() // start agent 83 | 84 | defer func() { 85 | a.GetSession().Close() 86 | }() 87 | 88 | for { 89 | if a.IsClosed() { 90 | return 91 | } 92 | msg, err := conn.GetNextMessage() 93 | if errors.Is(err, acceptor.ErrMessageRateLimit) { 94 | continue 95 | } 96 | if err != nil { 97 | h.Warn("get next message error", zap.Error(err), zap.Int64("agent_id", a.AgentID)) 98 | return 99 | } 100 | if msg == nil { 101 | continue 102 | } 103 | 104 | h.processPacket(a, msg) 105 | } 106 | } 107 | 108 | func (h *HandlerService) processPacket(a *agent.Agent, data []byte) { 109 | if h.config.ReceiveMessageLog { 110 | h.Info("agent get new packet", 111 | zap.Int64("agent_id", a.AgentID), 112 | zap.String("remote_addr", a.RemoteAddr().String())) 113 | } 114 | 115 | in, err := h.messageServer.DecodeMessage(data) 116 | if err != nil { 117 | h.Error("decode packet error", zap.Error(err), zap.Int64("agent_id", a.AgentID)) 118 | return 119 | } 120 | 121 | h.dispatchMessage(unHandlerMessage{ 122 | ctx: context.Background(), 123 | sess: a.GetSession(), 124 | in: in, 125 | }) 126 | 127 | a.SetLastAt() 128 | } 129 | -------------------------------------------------------------------------------- /latency/latency.go: -------------------------------------------------------------------------------- 1 | package latency 2 | 3 | import ( 4 | "strconv" 5 | "sync" 6 | "time" 7 | 8 | cb "github.com/emirpasic/gods/queues/circularbuffer" 9 | "github.com/metagogs/gogs/gslog" 10 | "go.uber.org/zap" 11 | ) 12 | 13 | type msgTime struct { 14 | agentID int64 15 | time int64 16 | } 17 | 18 | // LatencyServer 延时管理器 19 | type LatencyServer struct { 20 | maxQueueCount int //取最近多少次的平均延时 21 | cache sync.Map 22 | latencyQueue sync.Map 23 | agentLatency sync.Map 24 | // agentCallback map[int64]func(latency int64) 25 | agentCallback sync.Map 26 | systemLatency *cb.Queue 27 | 28 | *zap.Logger 29 | } 30 | 31 | func NewLatencyServer() *LatencyServer { 32 | return &LatencyServer{ 33 | maxQueueCount: 10, 34 | Logger: gslog.NewLog("latency"), 35 | systemLatency: cb.New(10), 36 | } 37 | } 38 | 39 | func (l *LatencyServer) OnUpdate(agentID int64, fn func(latency int64)) { 40 | l.agentCallback.Store(agentID, fn) 41 | } 42 | 43 | func (l *LatencyServer) Ping(agentID int64, sendTime int64) { 44 | key := strconv.FormatInt(agentID, 10) + strconv.FormatInt(sendTime, 10) 45 | l.cache.Store(key, &msgTime{agentID: agentID, time: sendTime}) 46 | if _, ok := l.latencyQueue.Load(agentID); !ok { 47 | l.latencyQueue.Store(agentID, cb.New(l.maxQueueCount)) 48 | } 49 | } 50 | 51 | func (l *LatencyServer) Pong(agentID int64, revice string) { 52 | reviceTime := time.Now().UnixMilli() 53 | 54 | key := strconv.FormatInt(agentID, 10) + revice 55 | if v, ok := l.cache.Load(key); ok { //nolint 56 | if m, ok := v.(*msgTime); ok { 57 | l.cache.Delete(key) 58 | if q, exist := l.latencyQueue.Load(agentID); exist { 59 | if queue, ok := q.(*cb.Queue); ok { 60 | queue.Enqueue((reviceTime - m.time) / 2) 61 | l.getTime(agentID, queue) 62 | } 63 | } 64 | } 65 | } 66 | } 67 | 68 | func (l *LatencyServer) getTime(agentID int64, q *cb.Queue) { 69 | //算一次延时 70 | var total int64 71 | var count int 72 | for _, t := range q.Values() { 73 | if v, ok := t.(int64); ok { 74 | total += v 75 | count++ 76 | } 77 | } 78 | 79 | if count > 0 { 80 | newLatencyTime := total / int64(count) 81 | l.agentLatency.Store(agentID, newLatencyTime) 82 | l.Info("get latency", 83 | zap.Int64("agent_id", agentID), 84 | zap.Int64("latency", newLatencyTime)) 85 | 86 | if fn, ok := l.agentLatency.Load(agentID); ok { 87 | if fn, ok := fn.(func(latency int64)); ok { 88 | fn(newLatencyTime) 89 | } 90 | } 91 | l.GetSystemLatency() 92 | } 93 | } 94 | 95 | func (l *LatencyServer) GetSystemLatency() int64 { 96 | var total int64 97 | var count int 98 | 99 | l.agentLatency.Range(func(key, value interface{}) bool { 100 | if v, ok := value.(int64); ok { 101 | total += v 102 | count++ 103 | } 104 | return true 105 | }) 106 | 107 | if count == 0 { 108 | return 0 109 | } 110 | 111 | systemTime := total / int64(count) 112 | l.systemLatency.Enqueue(systemTime) 113 | 114 | return systemTime 115 | } 116 | 117 | func (l *LatencyServer) GetSystemLatencyList() []int64 { 118 | var list []int64 119 | for _, v := range l.systemLatency.Values() { 120 | if v, ok := v.(int64); ok { 121 | list = append(list, v) 122 | } 123 | } 124 | 125 | return list 126 | } 127 | 128 | func (l *LatencyServer) GetUserLatency() map[int64]int64 { 129 | var result = make(map[int64]int64) 130 | l.agentLatency.Range(func(key, value interface{}) bool { 131 | if v, ok := value.(int64); ok { 132 | result[key.(int64)] = v 133 | } 134 | return true 135 | }) 136 | return result 137 | } 138 | 139 | func (l *LatencyServer) Clear(agentID int64) { 140 | l.latencyQueue.Delete(agentID) 141 | l.agentLatency.Delete(agentID) 142 | l.agentCallback.Delete(agentID) 143 | } 144 | -------------------------------------------------------------------------------- /message/message.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/metagogs/gogs/codec" 7 | "github.com/metagogs/gogs/component" 8 | "github.com/metagogs/gogs/dispatch" 9 | "github.com/metagogs/gogs/packet" 10 | "github.com/metagogs/gogs/session" 11 | ) 12 | 13 | type MessageServer struct { 14 | codecHelper *codec.CodecHelper 15 | dispatchServer *dispatch.DispatchServer 16 | } 17 | 18 | func NewMessageServer(codecHelper *codec.CodecHelper, dispatchServer *dispatch.DispatchServer) *MessageServer { 19 | return &MessageServer{ 20 | codecHelper: codecHelper, 21 | dispatchServer: dispatchServer, 22 | } 23 | } 24 | 25 | // DecodeMessage 解码消息 26 | func (m *MessageServer) DecodeMessage(data []byte) (*packet.Packet, error) { 27 | return m.codecHelper.Decode(data) 28 | } 29 | 30 | // CallMessageHandler 调用消息处理器 31 | func (m *MessageServer) CallMessageHandler(ctx context.Context, sess *session.Session, in *packet.Packet) error { 32 | return m.dispatchServer.Call(ctx, sess, in) 33 | } 34 | 35 | // EncodeMessage 编码消息 36 | func (m *MessageServer) EncodeMessage(in interface{}, name ...string) (*packet.Packet, error) { 37 | return m.codecHelper.Encode(in, name...) 38 | } 39 | 40 | // RegisterComponent 注册组件 41 | func (m *MessageServer) RegisterComponent(sd component.ComponentDesc, ss interface{}) { 42 | m.dispatchServer.RegisterComponent(sd, ss) 43 | } 44 | 45 | // UseDefaultEncodeJSON 设置默认编码器 46 | func (m *MessageServer) UseDefaultEncodeJSON() { 47 | m.codecHelper.RegisterEncode(codec.CodecJSONData, &codec.JSONEncode{}) 48 | } 49 | 50 | // UseDefaultEncodeJSON 设置默认编码器 51 | func (m *MessageServer) UseDefaultEncodeProto() { 52 | m.codecHelper.RegisterEncode(codec.CodecProtoData, &codec.ProtoEncode{}) 53 | } 54 | 55 | // UseDefaultEncodeJSON 设置默认编码器 56 | func (m *MessageServer) UseDefaultEncodePureJSON() { 57 | // 发送消息时不包含头部,使用JSON,方便测试使用,纯JSON 58 | m.codecHelper.RegisterEncode(codec.CodecJSONDataNoHeader, &codec.JSONEncode{}) 59 | } 60 | -------------------------------------------------------------------------------- /networkentity/networkentity.go: -------------------------------------------------------------------------------- 1 | package networkentity 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/metagogs/gogs/acceptor" 7 | "github.com/metagogs/gogs/packet" 8 | ) 9 | 10 | // NetworkEntity is a network entity that can be used in session 11 | type NetworkEntity interface { 12 | GetId() int64 // get the id of the network entity 13 | Stop() error // stop the network entity 14 | Send(in interface{}, name ...string) error // send message with network entity, name is the in typename 15 | SendData(data []byte) // send bytes data directly 16 | SendPacket(data *packet.Packet) // send packet 17 | RemoteAddr() net.Addr // get the network entity's remote address 18 | LocalAddr() net.Addr // get the network entity's local address 19 | GetLastTimeOnline() int64 // get the last time online 20 | GetLatency() int64 // get the latency 21 | GetConnInfo() *acceptor.ConnInfo // get the connection info 22 | } 23 | -------------------------------------------------------------------------------- /packet/benchmark_test.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import "testing" 4 | 5 | func BenchmarkParsePacket(b *testing.B) { 6 | b.ResetTimer() 7 | for i := 0; i < b.N; i++ { 8 | p, _ := ParsePacket([]byte{0x7E, 0x0A, 0x41, 0x00, 0x02, 0x00, 0x00, 0x0F, 0x0A, 0x0D, 0x31, 0x36, 0x36, 0x30, 0x33, 0x36, 0x30, 0x39, 0x31, 0x35, 0x35, 0x35, 0x36}) 9 | PutPacket(p) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packet/constants.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import "errors" 4 | 5 | const ( 6 | // 协议头长度 7 | HeaderLength = 8 8 | // 协议头标识 9 | HeaderFlag = 0x7E 10 | // 最大内容程度,16MBB 11 | MaxPacketSize = 1 << 24 12 | ) 13 | 14 | type PacketType byte 15 | 16 | const ( 17 | // 系统内置包,如Ping Pong 18 | SystemPacket PacketType = 0x01 19 | // 服务类型包,如Login、Register 20 | ServicePacket PacketType = 0x02 21 | ) 22 | 23 | var ErrHeaderFlag = errors.New("wrong header flag") 24 | var ErrHeaderLength = errors.New("wrong header length") 25 | var ErrMaxPacketSize = errors.New("max packet size") 26 | 27 | var ErrEncodeType = errors.New("wrong encode type") 28 | -------------------------------------------------------------------------------- /packet/packet.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "github.com/metagogs/gogs/utils/bytebuffer" 5 | ) 6 | 7 | // package header 64bit 8 | 9 | // 0byte flag: 0x7E 8bit 协议头固定 10 | 11 | // 1byte version: 5bit 协议版本 12 | // encodeType: 3bit 协议编码类型 13 | 14 | // 2byte packetType: 2bit 消息类型 0x01: system, 0x02: service 15 | // module: 6bit 消息模块 16 | 17 | // 3.4byte action: 16bit 消息ID 18 | 19 | // 5.6.7byte length: 24bit 消息长度 20 | type Packet struct { 21 | Bean interface{} 22 | HeaderByte []byte 23 | Data []byte 24 | } 25 | 26 | // ParsePacket 从字节数组中解析出Packet 27 | func ParsePacket(data []byte) (*Packet, error) { 28 | dataLength := len(data) 29 | if dataLength < HeaderLength { 30 | return nil, ErrHeaderLength 31 | } 32 | if dataLength > MaxPacketSize { 33 | return nil, ErrMaxPacketSize 34 | } 35 | 36 | header := data[:HeaderLength] 37 | //判断标识符 38 | if header[0] != HeaderFlag { 39 | return nil, ErrHeaderFlag 40 | } 41 | 42 | packetType := PacketType(header[2] >> 6) 43 | if packetType != SystemPacket && packetType != ServicePacket { 44 | return nil, ErrEncodeType 45 | } 46 | 47 | packet := GetPacket() 48 | packet.HeaderByte = header 49 | packet.Data = data[HeaderLength:] 50 | 51 | return packet, nil 52 | } 53 | 54 | func NewPacket(data []byte) *Packet { 55 | packet := GetPacket() 56 | packet.Data = data 57 | packet.HeaderByte[0] = 0x20 58 | 59 | return packet 60 | } 61 | 62 | func NewPacketWithHeader(data []byte, version, encodeType uint8, action uint32) *Packet { 63 | packet := GetPacket() 64 | packet.HeaderByte[0] = HeaderFlag 65 | packet.HeaderByte[1] = version<<3 | encodeType 66 | packet.HeaderByte[2] = byte(action >> 16) 67 | packet.HeaderByte[3] = byte(action >> 8) 68 | packet.HeaderByte[4] = byte(action) 69 | length := len(data) 70 | packet.HeaderByte[5] = byte(length >> 16) 71 | packet.HeaderByte[6] = byte(length >> 8) 72 | packet.HeaderByte[7] = byte(length) 73 | packet.Data = data 74 | 75 | return packet 76 | } 77 | 78 | func (p *Packet) ToData() *bytebuffer.ByteBuffer { 79 | defer func() { 80 | PutPacket(p) 81 | }() 82 | 83 | b := bytebuffer.Get() 84 | if len(p.HeaderByte) > 0 && p.HeaderByte[0] == HeaderFlag { 85 | _, _ = b.Write(p.HeaderByte) 86 | } 87 | _, _ = b.Write(p.Data) 88 | 89 | return b 90 | } 91 | 92 | func (p *Packet) ToByte() []byte { 93 | defer func() { 94 | PutPacket(p) 95 | }() 96 | 97 | length := len(p.Data) 98 | if len(p.HeaderByte) > 0 && p.HeaderByte[0] == HeaderFlag { 99 | length += HeaderLength 100 | data := make([]byte, length) 101 | copy(data, p.HeaderByte) 102 | copy(data[HeaderLength:], p.Data) 103 | return data 104 | } 105 | data := make([]byte, length) 106 | copy(data, p.Data) 107 | return data 108 | } 109 | 110 | // packetType: 2bit // 0x01: system, 0x02: service 111 | // module: 6bit 112 | // action: 16bit 113 | // 合一起的值 协议头/2/3/4字节 114 | func (p *Packet) GetActionKey() uint32 { 115 | return uint32(p.HeaderByte[2])<<16 | uint32(p.HeaderByte[3])<<8 | uint32(p.HeaderByte[4]) 116 | } 117 | 118 | func (p *Packet) GetPacketType() PacketType { 119 | return PacketType(p.HeaderByte[2] >> 6) 120 | } 121 | 122 | func (p *Packet) GetVersion() uint8 { 123 | return p.HeaderByte[1] >> 3 124 | } 125 | 126 | func (p *Packet) GetEncodeType() uint8 { 127 | return p.HeaderByte[1] & (0xff >> 5) 128 | } 129 | 130 | func (p *Packet) GetModule() uint8 { 131 | return p.HeaderByte[2] & (0xff >> 6) 132 | } 133 | 134 | func (p *Packet) GetLength() uint32 { 135 | return uint32(p.HeaderByte[5])<<16 | uint32(p.HeaderByte[6])<<8 | uint32(p.HeaderByte[7]) 136 | } 137 | -------------------------------------------------------------------------------- /packet/pool.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | var ( 8 | _pool = NewPool() 9 | 10 | GetPacket = _pool.GetPacket 11 | PutPacket = _pool.PutPacket 12 | ) 13 | 14 | type Pool struct { 15 | packet *sync.Pool 16 | } 17 | 18 | func NewPool() *Pool { 19 | p := &Pool{ 20 | packet: &sync.Pool{ 21 | New: func() interface{} { 22 | return &Packet{ 23 | HeaderByte: make([]byte, 8), 24 | } 25 | }, 26 | }, 27 | } 28 | 29 | return p 30 | } 31 | 32 | func (p Pool) GetPacket() *Packet { 33 | return p.packet.Get().(*Packet) 34 | } 35 | 36 | func (p Pool) PutPacket(bean *Packet) { 37 | p.packet.Put(bean) 38 | } 39 | -------------------------------------------------------------------------------- /packet/utils.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | func CreateHeader(data []byte, version, encodeType uint8, action uint32) []byte { 4 | headerByte := make([]byte, 8) 5 | headerByte[0] = HeaderFlag 6 | headerByte[1] = version<<3 | encodeType 7 | headerByte[2] = byte(action >> 16) 8 | headerByte[3] = byte(action >> 8) 9 | headerByte[4] = byte(action) 10 | length := len(data) 11 | headerByte[5] = byte(length >> 16) 12 | headerByte[6] = byte(length >> 8) 13 | headerByte[7] = byte(length) 14 | 15 | return headerByte 16 | } 17 | 18 | func CreateAction(packetType PacketType, module uint8, action uint16) uint32 { 19 | return uint32(packetType)<<22 | uint32(module)<<16 | uint32(action) 20 | } 21 | 22 | func ActionToBytes(action uint32) []byte { 23 | return []byte{ 24 | byte(action >> 16), 25 | byte(action >> 8), 26 | byte(action), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packet/utils_test.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestCreateAction(t *testing.T) { 9 | type args struct { 10 | packetType PacketType 11 | module uint8 12 | action uint16 13 | } 14 | tests := []struct { 15 | name string 16 | args args 17 | want uint32 18 | }{ 19 | { 20 | name: "Ping", 21 | args: args{ 22 | packetType: SystemPacket, 23 | module: 1, 24 | action: 1, 25 | }, 26 | want: 0x410001, //0100 0001 - 0000 0000 - 0000 0001 27 | }, 28 | { 29 | name: "Pong", 30 | args: args{ 31 | packetType: SystemPacket, 32 | module: 1, 33 | action: 2, 34 | }, 35 | want: 0x410002, //0100 0001 - 0000 0000 - 0000 0010 36 | }, 37 | { 38 | name: "ServicePacket", 39 | args: args{ 40 | packetType: ServicePacket, 41 | module: 2, 42 | action: 2, 43 | }, 44 | want: 0x820002, //1000 0010 - 0000 0000 - 0000 0010 45 | }, 46 | { 47 | name: "ServicePacket2", 48 | args: args{ 49 | packetType: ServicePacket, 50 | module: 15, 51 | action: 3, 52 | }, 53 | want: 0x8F0003, //1000 1111 - 0000 0000 - 0000 0011 54 | }, 55 | } 56 | for _, tt := range tests { 57 | t.Run(tt.name, func(t *testing.T) { 58 | if got := CreateAction(tt.args.packetType, tt.args.module, tt.args.action); got != tt.want { 59 | t.Errorf("CreateAction() = %v, want %v", got, tt.want) 60 | } 61 | }) 62 | } 63 | } 64 | 65 | func TestActionToBytes(t *testing.T) { 66 | type args struct { 67 | action uint32 68 | } 69 | tests := []struct { 70 | name string 71 | args args 72 | want []byte 73 | }{ 74 | { 75 | name: "Ping", 76 | args: args{ 77 | action: 0x410001, 78 | }, 79 | want: []byte{0x41, 0x00, 0x01}, 80 | }, 81 | { 82 | name: "Pong", 83 | args: args{ 84 | action: 0x410002, 85 | }, 86 | want: []byte{0x41, 0x00, 0x02}, 87 | }, 88 | { 89 | name: "ServicePacket", 90 | args: args{ 91 | action: 0x820002, 92 | }, 93 | want: []byte{0x82, 0x00, 0x02}, 94 | }, 95 | { 96 | name: "ServicePacket2", 97 | args: args{ 98 | action: 0x8F30a3, 99 | }, 100 | want: []byte{0x8f, 0x30, 0xa3}, 101 | }, 102 | } 103 | for _, tt := range tests { 104 | t.Run(tt.name, func(t *testing.T) { 105 | if got := ActionToBytes(tt.args.action); !reflect.DeepEqual(got, tt.want) { 106 | t.Errorf("ActionToBytes() = %v, want %v", got, tt.want) 107 | } 108 | }) 109 | } 110 | } 111 | 112 | func TestCreateHeader(t *testing.T) { 113 | type args struct { 114 | data []byte 115 | version uint8 116 | encodeType uint8 117 | action uint32 118 | } 119 | tests := []struct { 120 | name string 121 | args args 122 | want []byte 123 | }{ 124 | { 125 | name: "create action", 126 | args: args{ 127 | data: []byte{}, 128 | version: 1, 129 | encodeType: 2, 130 | action: 0x410002, 131 | }, 132 | want: []byte{0x7E, 0x0A, 0x41, 0x00, 0x02, 0x00, 0x00, 0x00}, 133 | }, 134 | } 135 | for _, tt := range tests { 136 | t.Run(tt.name, func(t *testing.T) { 137 | if got := CreateHeader(tt.args.data, tt.args.version, tt.args.encodeType, tt.args.action); !reflect.DeepEqual(got, tt.want) { 138 | t.Errorf("CreateHeader() = %v, want %v", got, tt.want) 139 | } 140 | }) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /proto/common.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package gogs; 4 | option go_package = "/proto"; 5 | 6 | 7 | // @gogs:Request 8 | message Ping { 9 | string time = 1; 10 | } 11 | 12 | message Pong { 13 | string time = 1; 14 | } 15 | 16 | // @gogs:Components 17 | message Components { 18 | Network Network = 1; 19 | } 20 | 21 | message Network { 22 | Ping Ping = 1; 23 | Pong Pong = 2; 24 | } -------------------------------------------------------------------------------- /session/constants.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrSessionNotFound = errors.New("session not found") 7 | ) 8 | -------------------------------------------------------------------------------- /session/session.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/metagogs/gogs/acceptor" 7 | "github.com/metagogs/gogs/networkentity" 8 | "github.com/metagogs/gogs/packet" 9 | "go.uber.org/zap" 10 | ) 11 | 12 | type Session struct { 13 | uid string // session/user id 14 | id int64 // agent id 15 | pool *sessionPoolImpl // session pool 16 | sessionLog *zap.Logger // session log 17 | agent networkentity.NetworkEntity // agent 18 | data SessionData // session data 19 | OnCloseCallbacks []func(id int64) // close callback 20 | } 21 | 22 | func (sess *Session) Close() { 23 | sess.log().Info("session close") 24 | sess.pool.DeleteSession(sess.id) 25 | if len(sess.uid) != 0 { 26 | sess.pool.deleteSessionByUID(sess.uid, sess.id) 27 | } 28 | _ = sess.agent.Stop() 29 | } 30 | 31 | func (sess *Session) log() *zap.Logger { 32 | return sess.sessionLog.With(zap.Int64("agent_id", sess.id)) 33 | } 34 | 35 | func (sess *Session) ID() int64 { 36 | return sess.agent.GetId() 37 | } 38 | 39 | func (sess *Session) IDString() string { 40 | return strconv.FormatInt(sess.ID(), 10) 41 | } 42 | 43 | func (sess *Session) UID() string { 44 | return sess.uid 45 | } 46 | 47 | func (sess *Session) SetUID(uid string) { 48 | // delete old data before set new uid 49 | if len(sess.uid) > 0 { 50 | sess.pool.deleteSessionByUID(sess.uid, sess.id) 51 | } 52 | sess.uid = uid 53 | if len(sess.uid) == 0 { 54 | return 55 | } 56 | sess.pool.addSessionByUID(uid, sess) 57 | } 58 | 59 | func (sess *Session) IsLogin() bool { 60 | return len(sess.uid) > 0 61 | } 62 | 63 | func (sess *Session) SendMessage(in interface{}, name ...string) error { 64 | if sess.pool.config.SendMessageLog { 65 | sess.log().Info("send message") 66 | } 67 | return sess.agent.Send(in, name...) 68 | } 69 | 70 | func (sess *Session) SendData(data []byte) { 71 | sess.agent.SendData(data) 72 | } 73 | 74 | func (sess *Session) SendPacket(data *packet.Packet) { 75 | sess.agent.SendPacket(data) 76 | } 77 | 78 | func (sess *Session) GetLastTimeOnline() int64 { 79 | return sess.agent.GetLastTimeOnline() 80 | } 81 | 82 | func (sess *Session) GetData() SessionData { 83 | return sess.data 84 | } 85 | 86 | func (sess *Session) OnClose(c func(id int64)) error { 87 | sess.OnCloseCallbacks = append(sess.OnCloseCallbacks, c) 88 | return nil 89 | } 90 | 91 | func (sess *Session) GetOnCloseCallbacks() []func(id int64) { 92 | return sess.OnCloseCallbacks 93 | } 94 | 95 | func (sess *Session) SetOnCloseCallbacks(callbacks []func(id int64)) { 96 | sess.OnCloseCallbacks = callbacks 97 | } 98 | 99 | func (sess *Session) SetOnCloseCallback(c func(id int64)) { 100 | sess.OnCloseCallbacks = []func(id int64){c} 101 | } 102 | 103 | func (sess *Session) GetLatency() int64 { 104 | return sess.agent.GetLatency() 105 | } 106 | 107 | func (sess *Session) GetConnInfo() *acceptor.ConnInfo { 108 | return sess.agent.GetConnInfo() 109 | } 110 | -------------------------------------------------------------------------------- /session/session_data.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | type SessionData interface { 4 | GetData() map[string]interface{} 5 | Set(key string, value interface{}) 6 | Get(key string) (interface{}, bool) 7 | GetString(key, def string) string 8 | Delete(key string) 9 | } 10 | -------------------------------------------------------------------------------- /session/session_memory.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import "sync" 4 | 5 | type SessionMemory struct { 6 | sync.RWMutex 7 | data map[string]interface{} 8 | } 9 | 10 | func NewSessionMemory() *SessionMemory { 11 | return &SessionMemory{ 12 | data: make(map[string]interface{}), 13 | } 14 | } 15 | 16 | // GetData gets the data 17 | func (sess *SessionMemory) GetData() map[string]interface{} { 18 | sess.RLock() 19 | defer sess.RUnlock() 20 | 21 | return sess.data 22 | } 23 | 24 | // Set associates value with the key in session storage 25 | func (sess *SessionMemory) Set(key string, value interface{}) { 26 | sess.Lock() 27 | defer sess.Unlock() 28 | 29 | sess.data[key] = value 30 | } 31 | 32 | func (sess *SessionMemory) Get(key string) (interface{}, bool) { 33 | sess.RLock() 34 | defer sess.RUnlock() 35 | 36 | value, ok := sess.data[key] 37 | return value, ok 38 | } 39 | 40 | func (sess *SessionMemory) Delete(key string) { 41 | sess.RLock() 42 | defer sess.RUnlock() 43 | 44 | delete(sess.data, key) 45 | } 46 | 47 | func (sess *SessionMemory) GetString(key, def string) string { 48 | sess.RLock() 49 | defer sess.RUnlock() 50 | 51 | value, ok := sess.data[key] 52 | if ok { 53 | return value.(string) 54 | } 55 | 56 | return def 57 | } 58 | -------------------------------------------------------------------------------- /session/session_memory_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNewSessionMemory(t *testing.T) { 10 | m := NewSessionMemory() 11 | out, exist := m.Get("test") 12 | assert.False(t, exist) 13 | assert.Nil(t, out) 14 | 15 | m.Set("test", "test") 16 | out, exist = m.Get("test") 17 | assert.True(t, exist) 18 | assert.Equal(t, "test", out) 19 | 20 | out = m.GetString("test", "") 21 | assert.Equal(t, "test", out) 22 | 23 | res := m.GetData() 24 | assert.Equal(t, 1, len(res)) 25 | assert.Equal(t, "test", res["test"]) 26 | 27 | m.Delete("test") 28 | out, exist = m.Get("test") 29 | assert.False(t, exist) 30 | assert.Nil(t, out) 31 | 32 | out = m.GetString("test", "default") 33 | assert.Equal(t, "default", out) 34 | 35 | } 36 | -------------------------------------------------------------------------------- /static.go: -------------------------------------------------------------------------------- 1 | package gogs 2 | 3 | import ( 4 | "github.com/metagogs/gogs/message" 5 | "github.com/metagogs/gogs/packet" 6 | "github.com/metagogs/gogs/session" 7 | "github.com/metagogs/gogs/utils/slicex" 8 | ) 9 | 10 | var DefaultSessionPool session.SessionPool 11 | var DefaultMessageServer *message.MessageServer 12 | 13 | func ListSessions() []*session.Session { 14 | return DefaultSessionPool.ListSessions() 15 | } 16 | 17 | func GetSessionByID(id int64) (*session.Session, error) { 18 | return DefaultSessionPool.GetSessionByID(id) 19 | } 20 | 21 | // GetSessionIDsByUID get session by user id. 22 | // the filter is used to filter the sessions that should not receive the message. 23 | // result first is the session ids that match the filter, result second is the all session ids with the given uid. 24 | func GetSessionIDsByUID(uid string, filter *session.SessionFilter) ([]int64, []int64) { 25 | return DefaultSessionPool.GetSessionByUID(uid, filter) 26 | } 27 | 28 | // GetSessionByUID get session by user id. 29 | func GetSessionByUID(uid string, filter *session.SessionFilter) []*session.Session { 30 | list, _ := DefaultSessionPool.GetSessionByUID(uid, filter) 31 | result := make([]*session.Session, 0) 32 | for _, sess := range list { 33 | session, err := DefaultSessionPool.GetSessionByID(sess) 34 | if err == nil { 35 | result = append(result, session) 36 | } 37 | } 38 | 39 | return result 40 | } 41 | 42 | // SendMessageByID send message to the session with the given id. 43 | func SendMessageByID(sessionId int64, in interface{}) { 44 | if sess, err := DefaultSessionPool.GetSessionByID(sessionId); err == nil { 45 | _ = sess.SendMessage(in) 46 | } 47 | } 48 | 49 | func SendDataByID(sessionId int64, in []byte) { 50 | if sess, err := DefaultSessionPool.GetSessionByID(sessionId); err == nil { 51 | sess.SendData(in) 52 | } 53 | } 54 | 55 | func SendPacketByID(sessionId int64, in *packet.Packet) { 56 | if sess, err := DefaultSessionPool.GetSessionByID(sessionId); err == nil { 57 | sess.SendPacket(in) 58 | } 59 | } 60 | 61 | // BroadcastMessage broadcast message to all sessions except the session with the given id. 62 | // the filter is used to filter the sessions that should not receive the message. 63 | // send packet 64 | func BroadcastMessage(users []string, send interface{}, filter *session.SessionFilter, exclude ...string) error { 65 | packet, err := EncodeMessage(send) 66 | if err != nil { 67 | return err 68 | } 69 | for _, u := range users { 70 | if slicex.InSlice(u, exclude) { 71 | continue 72 | } 73 | if result, _ := GetSessionIDsByUID(u, filter); len(result) > 0 { 74 | SendPacketByID(result[0], packet) 75 | } 76 | } 77 | 78 | return nil 79 | } 80 | 81 | // send bytes 82 | func BroadcastData(users []string, data []byte, filter *session.SessionFilter, exclude ...string) { 83 | for _, u := range users { 84 | if slicex.InSlice(u, exclude) { 85 | continue 86 | } 87 | if result, _ := GetSessionIDsByUID(u, filter); len(result) > 0 { 88 | SendDataByID(result[0], data) 89 | } 90 | } 91 | } 92 | 93 | func EncodeMessage(in interface{}, name ...string) (*packet.Packet, error) { 94 | packet, err := DefaultMessageServer.EncodeMessage(in, name...) 95 | if err != nil { 96 | return nil, err 97 | } 98 | return packet, nil 99 | } 100 | -------------------------------------------------------------------------------- /system.go: -------------------------------------------------------------------------------- 1 | package gogs 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/metagogs/gogs/proto" 7 | "github.com/metagogs/gogs/session" 8 | ) 9 | 10 | type NetworkComponent struct { 11 | app *App 12 | } 13 | 14 | func NewNetworkComponent(app *App) *NetworkComponent { 15 | return &NetworkComponent{ 16 | app: app, 17 | } 18 | } 19 | 20 | func (s *NetworkComponent) Pong(ctx context.Context, sess *session.Session, pong *proto.Pong) { 21 | // go s.app.LatencyServer.Pong(sess.ID(), pong.Time) 22 | } 23 | -------------------------------------------------------------------------------- /system/system.ep.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | 7 | "github.com/metagogs/gogs/component" 8 | "github.com/metagogs/gogs/message" 9 | "github.com/metagogs/gogs/packet" 10 | "github.com/metagogs/gogs/proto" 11 | "github.com/metagogs/gogs/session" 12 | ) 13 | 14 | func RegisterSystemComponent(s *message.MessageServer, srv NetworkComponent) { 15 | s.RegisterComponent(_NetworkComponentDesc, srv) 16 | } 17 | 18 | type NetworkComponent interface { 19 | Pong(ctx context.Context, sess *session.Session, pong *proto.Pong) 20 | } 21 | 22 | func _NetworkComponent_Pong_Handler(srv interface{}, ctx context.Context, sess *session.Session, in interface{}) { 23 | srv.(NetworkComponent).Pong(ctx, sess, in.(*proto.Pong)) 24 | } 25 | 26 | var _NetworkComponentDesc = component.ComponentDesc{ 27 | ComponentName: "NetworkComponent", 28 | ComponentIndex: 1, // equal to module index 29 | ComponentType: (*NetworkComponent)(nil), 30 | Methods: []component.ComponentMethodDesc{ 31 | { 32 | MethodIndex: packet.CreateAction(packet.SystemPacket, 1, 1), 33 | FieldType: reflect.TypeOf(proto.Ping{}), 34 | Handler: nil, 35 | FieldHandler: func() interface{} { 36 | return new(proto.Ping) 37 | }, 38 | }, 39 | { 40 | MethodIndex: packet.CreateAction(packet.SystemPacket, 1, 2), 41 | FieldType: reflect.TypeOf(proto.Pong{}), 42 | Handler: _NetworkComponent_Pong_Handler, 43 | FieldHandler: func() interface{} { 44 | return new(proto.Pong) 45 | }, 46 | }, 47 | }, 48 | } 49 | -------------------------------------------------------------------------------- /tools/gogs/Makefile: -------------------------------------------------------------------------------- 1 | version := $(shell /bin/date "+%Y-%m-%d %H:%M") 2 | 3 | build: 4 | go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" -o gogs main.go 5 | $(if $(shell command -v upx), upx gogs) 6 | mac: 7 | GOOS=darwin go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" -o gogs main.go 8 | $(if $(shell command -v upx), upx gogs) 9 | win: 10 | GOOS=windows go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" -o gogs.exe main.go 11 | $(if $(shell command -v upx), upx gogs.exe) 12 | linux: 13 | GOOS=linux go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" -o gogs main.go 14 | $(if $(shell command -v upx), upx gogs) 15 | -------------------------------------------------------------------------------- /tools/gogs/cmd/csharp.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/metagogs/gogs/tools/gogs/csharp" 8 | "github.com/pterm/pterm" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func init() { 13 | RootCmd.AddCommand(csharpCmd) 14 | csharpCmd.Flags().StringVarP(&protoFile, "file", "f", "", "proto file") 15 | csharpCmd.Flags().BoolVarP(&gogsFiles, "gogs", "g", false, "should generate gogs framework code") 16 | } 17 | 18 | var csharpCmd = &cobra.Command{ 19 | Use: "csharp", 20 | Short: "generate csharp code", 21 | Long: ``, 22 | Run: func(cmd *cobra.Command, args []string) { 23 | if len(protoFile) == 0 { 24 | pterm.Error.Printfln("proto file is empty") 25 | os.Exit(1) 26 | } 27 | 28 | fmt.Println(gogsFiles) 29 | gen, err := csharp.NewCSharpGen(protoFile, gogsFiles) 30 | if err != nil { 31 | fmt.Println(err) 32 | } 33 | _ = gen.Generate() 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /tools/gogs/cmd/docker.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/metagogs/gogs/tools/gogs/docker" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func init() { 11 | RootCmd.AddCommand(dockerCmd) 12 | dockerCmd.Flags().BoolVarP(&proxycn, "cn", "c", false, "use golang proxy for china") 13 | } 14 | 15 | var dockerCmd = &cobra.Command{ 16 | Use: "docker", 17 | Short: "generate docker file", 18 | Long: ``, 19 | Run: func(cmd *cobra.Command, args []string) { 20 | gen, err := docker.NewDockerGen(proxycn) 21 | if err != nil { 22 | fmt.Println(err) 23 | } 24 | _ = gen.Generate() 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /tools/gogs/cmd/flags.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | var ( 4 | protoFile string 5 | proxycn bool 6 | gogsFiles bool 7 | ) 8 | -------------------------------------------------------------------------------- /tools/gogs/cmd/init.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/metagogs/gogs/tools/gogs/gen" 5 | "github.com/pterm/pterm" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var ( 10 | gomodpath string 11 | gopackage string 12 | ) 13 | 14 | func init() { 15 | RootCmd.AddCommand(initCmd) 16 | initCmd.Flags().StringVarP(&gomodpath, "pakcage", "p", "", "the go mod package") 17 | } 18 | 19 | var initCmd = &cobra.Command{ 20 | Use: "init", 21 | Short: "init gogs project", 22 | Long: ``, 23 | Run: func(cmd *cobra.Command, args []string) { 24 | if len(gomodpath) == 0 { 25 | gomodpath = "gogs" 26 | pterm.Warning.Printfln("You didn't set the package name. I'll use the default package name - gogs") 27 | } 28 | initGen := gen.NewInit(gomodpath) 29 | err := initGen.Generate() 30 | if err != nil { 31 | pterm.Error.Println("generate error: " + err.Error()) 32 | } 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /tools/gogs/cmd/proto.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/metagogs/gogs/tools/gogs/gen" 8 | "github.com/metagogs/gogs/utils/gomod" 9 | "github.com/pterm/pterm" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func init() { 14 | RootCmd.AddCommand(protoCmd) 15 | protoCmd.Flags().StringVarP(&protoFile, "file", "f", "", "proto file") 16 | protoCmd.Flags().StringVarP(&gopackage, "package", "p", "", "go package") 17 | } 18 | 19 | var protoCmd = &cobra.Command{ 20 | Use: "go", 21 | Short: "generate gogs code", 22 | Long: ``, 23 | Run: func(cmd *cobra.Command, args []string) { 24 | if len(protoFile) == 0 { 25 | pterm.Error.Printfln("proto file is empty") 26 | os.Exit(1) 27 | } 28 | goPackage := gopackage 29 | if len(goPackage) == 0 { 30 | if _, err := os.Stat("go.mod"); err != nil { 31 | pterm.Error.Printfln("go.mod not found") 32 | os.Exit(1) 33 | } 34 | goModule, err := gomod.GetMod() 35 | if err != nil { 36 | pterm.Error.Printfln("get go mod error: " + err.Error()) 37 | os.Exit(1) 38 | } 39 | if !goModule.IsInGoMod() { 40 | pterm.Error.Printfln("not in go mod mode") 41 | os.Exit(1) 42 | } 43 | 44 | goPackage = goModule.Path 45 | } 46 | 47 | gen, err := gen.NewGen(protoFile, goPackage) 48 | if err != nil { 49 | fmt.Println(err) 50 | } 51 | _ = gen.Generate() 52 | }, 53 | } 54 | -------------------------------------------------------------------------------- /tools/gogs/cmd/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "github.com/spf13/cobra" 20 | ) 21 | 22 | // RootCmd represents the base command when called without any subcommands 23 | var RootCmd = &cobra.Command{ 24 | Use: "gs", 25 | Short: "gogs开发工具", 26 | Long: ``, 27 | Run: func(cmd *cobra.Command, args []string) { 28 | _ = cmd.Help() 29 | }, 30 | } 31 | 32 | // Execute ... 33 | func Execute() { 34 | cobra.CheckErr(RootCmd.Execute()) 35 | } 36 | -------------------------------------------------------------------------------- /tools/gogs/csharp/gen_test.go: -------------------------------------------------------------------------------- 1 | package csharp 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/metagogs/gogs/utils/filex" 8 | ) 9 | 10 | func TestNewGen(t *testing.T) { 11 | type args struct { 12 | proto string 13 | onlyCode bool 14 | } 15 | tests := []struct { 16 | name string 17 | args args 18 | wantErr bool 19 | }{ 20 | { 21 | name: "new gen", 22 | args: args{ 23 | proto: "testdata/data.proto", 24 | onlyCode: false, 25 | }, 26 | wantErr: false, 27 | }, 28 | } 29 | for _, tt := range tests { 30 | t.Run(tt.name, func(t *testing.T) { 31 | g, err := NewCSharpGen(tt.args.proto, tt.args.onlyCode) 32 | g.Home = "test/" 33 | g.debugNoPb = true 34 | if (err != nil) != tt.wantErr { 35 | t.Errorf("NewGen() error = %v, wantErr %v", err, tt.wantErr) 36 | return 37 | } 38 | _ = g.Generate() 39 | if (err != nil) != tt.wantErr { 40 | t.Errorf("NewGen() error = %v, wantErr %v", err, tt.wantErr) 41 | return 42 | } 43 | 44 | var haveErr bool 45 | if ok := filex.IsFileEqual("testdata/Model/Register.cs", "test/Model/Register.cs"); !ok { 46 | t.Errorf("Init.Generate() error = %s is not equal to %s", "test/Model/Register.cs", "testdata/Model/Register.cs") 47 | haveErr = true 48 | } 49 | 50 | if !haveErr { 51 | _ = os.RemoveAll("test") 52 | } 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tools/gogs/csharp/gentemplate/Gogs/Codec.tpl: -------------------------------------------------------------------------------- 1 | // Code generated by gogs. DO NOT EDIT. 2 | // versions: {{.Version}} 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using Google.Protobuf; 7 | 8 | namespace Gogs 9 | { 10 | class ProtoEncode : IEncode 11 | { 12 | public byte[] Encode(IMessage msg) 13 | { 14 | if (msg is IMessage message) 15 | { 16 | return message.ToByteArray(); 17 | } 18 | 19 | throw new Exception("object is not the message type"); 20 | } 21 | } 22 | 23 | class ProtoDecode : IDecode 24 | { 25 | static readonly Dictionary parses = new Dictionary(); 26 | 27 | public T Decode(byte[] data) where T : IMessage, new() 28 | { 29 | if (parses.TryGetValue(typeof(T).Name, out MessageParser parse)) 30 | { 31 | return (T)parse.ParseFrom(data); 32 | } 33 | 34 | Google.Protobuf.MessageParser newParse = new Google.Protobuf.MessageParser(() => new T()); 35 | parses[typeof(T).Name] = newParse; 36 | return newParse.ParseFrom(data); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /tools/gogs/csharp/gentemplate/Gogs/EventsManager.tpl: -------------------------------------------------------------------------------- 1 | // Code generated by gogs. DO NOT EDIT. 2 | // versions: {{.Version}} 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using Google.Protobuf; 7 | 8 | namespace Gogs 9 | { 10 | public static class EventsManager 11 | { 12 | static readonly Dictionary> s_Events = new Dictionary>(); 13 | 14 | static readonly Dictionary> s_EventLookups = 15 | new Dictionary>(); 16 | 17 | public static void AddListener(Action evt) where T : IMessage 18 | { 19 | if (!s_EventLookups.ContainsKey(evt)) 20 | { 21 | Action newAction = (s, e) => evt(s, (T)e); 22 | s_EventLookups[evt] = newAction; 23 | 24 | if (s_Events.TryGetValue(typeof(T), out Action internalAction)) 25 | s_Events[typeof(T)] = internalAction += newAction; 26 | else 27 | s_Events[typeof(T)] = newAction; 28 | } 29 | } 30 | 31 | public static void RemoveListener(Action evt) where T : IMessage 32 | { 33 | if (s_EventLookups.TryGetValue(evt, out var action)) 34 | { 35 | if (s_Events.TryGetValue(typeof(T), out var tempAction)) 36 | { 37 | tempAction -= action; 38 | if (tempAction == null) 39 | s_Events.Remove(typeof(T)); 40 | else 41 | s_Events[typeof(T)] = tempAction; 42 | } 43 | 44 | s_EventLookups.Remove(evt); 45 | } 46 | } 47 | 48 | public static void Broadcast(string name, IMessage evt) 49 | { 50 | if (s_Events.TryGetValue(evt.GetType(), out var action)) 51 | action.Invoke(name, evt); 52 | } 53 | 54 | public static void Clear() 55 | { 56 | s_Events.Clear(); 57 | s_EventLookups.Clear(); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /tools/gogs/csharp/gentemplate/Gogs/ICodec.tpl: -------------------------------------------------------------------------------- 1 | // Code generated by gogs. DO NOT EDIT. 2 | // versions: {{.Version}} 3 | 4 | using Google.Protobuf; 5 | namespace Gogs 6 | { 7 | public interface IEncode 8 | { 9 | public byte[] Encode(IMessage msg); 10 | } 11 | 12 | public interface IDecode 13 | { 14 | public T Decode(byte[] data) where T : IMessage, new(); 15 | } 16 | } -------------------------------------------------------------------------------- /tools/gogs/csharp/gentemplate/Gogs/Messages.tpl: -------------------------------------------------------------------------------- 1 | // Code generated by gogs. DO NOT EDIT. 2 | // versions: {{.Version}} 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Reflection; 7 | using Google.Protobuf; 8 | 9 | namespace Gogs 10 | { 11 | class Messages 12 | { 13 | 14 | public const int CODEC_JSON_DATA_NO_HEADER = 0; 15 | public const int CODEC_JSON_DATA = 1; 16 | public const int CODEC_PROTO_DATA = 2; 17 | 18 | 19 | private static Messages instance = null; 20 | 21 | public static IEncode _encode = new ProtoEncode(); 22 | public static int _encodeType = CODEC_PROTO_DATA; 23 | public static Dictionary _decodes = new Dictionary(); 24 | 25 | 26 | public static Messages Instance() 27 | { 28 | if (instance == null) 29 | { 30 | instance = new Messages(); 31 | instance.AddDecode(CODEC_PROTO_DATA, new ProtoDecode()); 32 | // init common message 33 | Dispatch.AddField("Ping", typeof(Ping), Packet.CreateAction(1, 1, 1)); 34 | Dispatch.AddField("Pong", typeof(Pong), Packet.CreateAction(1, 1, 2)); 35 | } 36 | return instance; 37 | } 38 | 39 | public static void Message(string name, byte[] data) 40 | { 41 | if(Instance().Decode(data,out Packet packet)) 42 | { 43 | EventsManager.Broadcast(name, (IMessage)packet.obj); 44 | } 45 | } 46 | 47 | public void SetEncode(IEncode e, int type) 48 | { 49 | _encode = e; 50 | _encodeType = type; 51 | 52 | } 53 | 54 | public void AddDecode(int t, IDecode d) 55 | { 56 | _decodes[t] = d; 57 | } 58 | 59 | public bool Encode(Object msg, out Packet packet) 60 | { 61 | packet = null; 62 | int action = Dispatch.GetActionByName(msg.GetType().Name); 63 | if(msg is IMessage message) 64 | { 65 | byte[] data = _encode.Encode(message); 66 | packet = Packet.NewPacketWithHeader(data, 1, _encodeType, action); 67 | return true; 68 | } 69 | 70 | return false; 71 | 72 | } 73 | 74 | public bool Decode(byte[] data, out Packet packet) 75 | { 76 | if (!Packet.ParsePacket(data, out packet)) 77 | { 78 | return false; 79 | } 80 | 81 | if(_decodes.TryGetValue(packet.GetEncodeType(), out IDecode decode)) 82 | { 83 | Type t = Dispatch.GetTypeByAction(packet.GetActionKey()); 84 | 85 | MethodInfo mi = decode.GetType().GetMethod("Decode").MakeGenericMethod(new Type[] { t }); 86 | packet.obj = mi.Invoke(decode, new object[]{ packet.data }); 87 | return true; 88 | } 89 | 90 | return false; 91 | } 92 | 93 | } 94 | 95 | class Dispatch 96 | { 97 | static readonly Dictionary nameWithAction = new Dictionary(); 98 | static readonly Dictionary actionWithName = new Dictionary(); 99 | 100 | public static void AddField(String fieldName, Type t, int action) 101 | { 102 | nameWithAction[fieldName] = action; 103 | actionWithName[action] = t; 104 | } 105 | 106 | public static int GetActionByName(String fieldName) 107 | { 108 | return nameWithAction[fieldName]; 109 | } 110 | 111 | public static Type GetTypeByAction(int action) 112 | { 113 | return actionWithName[action]; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /tools/gogs/csharp/gentemplate/Gogs/Packet.tpl: -------------------------------------------------------------------------------- 1 | // Code generated by gogs. DO NOT EDIT. 2 | // versions: {{.Version}} 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace Gogs 8 | { 9 | public class Packet 10 | { 11 | public byte[] header; 12 | public byte[] data; 13 | public Object obj; 14 | 15 | public const int HeaderLength = 8; 16 | public const int HeaderFlag = 0X7e; 17 | 18 | 19 | public static int CreateAction(int packetType, int module, int action) 20 | { 21 | return packetType << 22 | module << 16 | action; 22 | } 23 | 24 | public static bool ParsePacket(byte[] data, out Packet packet) 25 | { 26 | packet = null; 27 | if (data.Length < 8) 28 | { 29 | return false; 30 | } 31 | if(data.Length > 1 << 24) 32 | { 33 | return false; 34 | } 35 | byte[] header = data[0..8]; 36 | if(header[0] != HeaderFlag) 37 | { 38 | return false; 39 | } 40 | packet = new Packet(); 41 | packet.header = header; 42 | packet.data = data[8..data.Length]; 43 | 44 | return true; 45 | } 46 | 47 | public static Packet NewPacketWithHeader(byte[] data, int version, int encodeType, int action) 48 | { 49 | byte[] header = new byte[8]; 50 | header[0] = 0x7E; 51 | header[1] = (byte)(version << 3 | encodeType); 52 | header[2] = (byte)(action >> 16); 53 | header[3] = (byte)(action >> 8); 54 | header[4] = (byte)(action); 55 | 56 | int len = data.Length; 57 | header[5] = (byte)(len >> 16); 58 | header[6] = (byte)(len >> 8); 59 | header[7] = (byte)(len); 60 | 61 | 62 | 63 | Packet packet = new Packet(); 64 | packet.header = header; 65 | packet.data = data; 66 | 67 | return packet; 68 | 69 | } 70 | 71 | public byte[] ToByteArray() 72 | { 73 | List packet = new List(); 74 | packet.AddRange(header); 75 | packet.AddRange(data); 76 | return packet.ToArray(); 77 | } 78 | 79 | 80 | public int GetEncodeType() 81 | { 82 | return header[1] & (0xff >> 5); 83 | } 84 | 85 | public int GetActionKey() 86 | { 87 | return (int)(header[2] << 16) | (int)(header[3] << 8) | (int)header[4]; 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /tools/gogs/csharp/gentemplate/Register.tpl: -------------------------------------------------------------------------------- 1 | using System; 2 | using Gogs; 3 | using Google.Protobuf; 4 | namespace {{.Package | CamelCase }} 5 | { 6 | public static class GInit 7 | { 8 | public const int SERVICE_TYPE = 2; 9 | 10 | public static void Init() 11 | { 12 | {{range .Components}}{{range .Fields}} 13 | Dispatch.AddField("{{.Name}}", typeof({{.Name}}), Packet.CreateAction(SERVICE_TYPE, {{.ComponentIndex}}, {{.Index}})); // {{.Action16}} {{.Action10}} 14 | {{end}}{{end}} 15 | Gogs.Messages.Instance(); 16 | } 17 | } 18 | 19 | public static class GMessages 20 | { 21 | public static void Message(string name, byte[] data) 22 | { 23 | Gogs.Messages.Message(name, data); 24 | } 25 | 26 | public static byte[] GetPong() 27 | { 28 | Pong pong = new Pong(); 29 | return pong.ToPacketData(); 30 | } 31 | } 32 | 33 | public static class MessageExtension 34 | { 35 | public static byte[] ToPacketData(this IMessage obj) 36 | { 37 | if (Gogs.Messages.Instance().Encode(obj, out Gogs.Packet packet)) 38 | { 39 | return packet.ToByteArray(); 40 | } 41 | 42 | return new byte[] { }; 43 | } 44 | } 45 | 46 | 47 | public static class GEvents 48 | { 49 | public static void OnPing(Action action) 50 | { 51 | Gogs.EventsManager.AddListener(action); 52 | } 53 | {{range .Components}}{{range .Fields}} 54 | public static void On{{.Name}}(Action action) 55 | { 56 | Gogs.EventsManager.AddListener<{{.Name}}>(action); 57 | } 58 | {{end}}{{end}} 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tools/gogs/csharp/gentemplate/template.go: -------------------------------------------------------------------------------- 1 | package gentemplate 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | //go:embed Gogs/Codec.tpl 8 | var CodecTpl string 9 | 10 | //go:embed Gogs/Common.tpl 11 | var CommonTpl string 12 | 13 | //go:embed Gogs/EventsManager.tpl 14 | var EventsManagerTpl string 15 | 16 | //go:embed Gogs/ICodec.tpl 17 | var ICodecTpl string 18 | 19 | //go:embed Gogs/Messages.tpl 20 | var MessagesTpl string 21 | 22 | //go:embed Gogs/Packet.tpl 23 | var PacketTpl string 24 | 25 | //go:embed Register.tpl 26 | var RegisterTpl string 27 | -------------------------------------------------------------------------------- /tools/gogs/csharp/testdata/Model/Register.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Gogs; 3 | using Google.Protobuf; 4 | namespace Model 5 | { 6 | public static class GInit 7 | { 8 | public const int SERVICE_TYPE = 2; 9 | 10 | public static void Init() 11 | { 12 | 13 | Dispatch.AddField("BindUser", typeof(BindUser), Packet.CreateAction(SERVICE_TYPE, 1, 1)); // 0x810001 8454145 14 | 15 | Dispatch.AddField("BindSuccess", typeof(BindSuccess), Packet.CreateAction(SERVICE_TYPE, 1, 2)); // 0x810002 8454146 16 | 17 | Gogs.Messages.Instance(); 18 | } 19 | } 20 | 21 | public static class GMessages 22 | { 23 | public static void Message(string name, byte[] data) 24 | { 25 | Gogs.Messages.Message(name, data); 26 | } 27 | 28 | public static byte[] GetPong() 29 | { 30 | Pong pong = new Pong(); 31 | return pong.ToPacketData(); 32 | } 33 | } 34 | 35 | public static class MessageExtension 36 | { 37 | public static byte[] ToPacketData(this IMessage obj) 38 | { 39 | if (Gogs.Messages.Instance().Encode(obj, out Gogs.Packet packet)) 40 | { 41 | return packet.ToByteArray(); 42 | } 43 | 44 | return new byte[] { }; 45 | } 46 | } 47 | 48 | 49 | public static class GEvents 50 | { 51 | public static void OnPing(Action action) 52 | { 53 | Gogs.EventsManager.AddListener(action); 54 | } 55 | 56 | public static void OnBindUser(Action action) 57 | { 58 | Gogs.EventsManager.AddListener(action); 59 | } 60 | 61 | public static void OnBindSuccess(Action action) 62 | { 63 | Gogs.EventsManager.AddListener(action); 64 | } 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tools/gogs/csharp/testdata/data.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package model; 4 | option go_package = "/model"; 5 | 6 | // the Main Components 7 | // only have one, include some child componetns 8 | // all the message in this struct is the componetns 9 | // @gogs:Components 10 | message Components { 11 | // don't care the filed name, we never use it 12 | // but you should be careful about the filed number 13 | BaseWorld BaseWorld = 1; 14 | } 15 | 16 | // componetns, 17 | // all the messages are used for communication between the client and the server 18 | message BaseWorld { 19 | // don't care the filed name, we never use it 20 | // but you should be careful about the filed number 21 | BindUser BindUser = 1; 22 | 23 | BindSuccess BindSuccess = 2; 24 | } 25 | // common message 26 | // the message is used for the client and the server to communicate 27 | // the corresponding method will be generated according to this message 28 | message BindUser { 29 | string uid = 1; 30 | } 31 | 32 | 33 | // it is only used for messages sent from the server to the client 34 | // the server will not receive the message and will not generate the corresponding method 35 | // @gogs:ServerMessage 36 | message BindSuccess { 37 | } -------------------------------------------------------------------------------- /tools/gogs/csharp/testdata/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/metagogs/gogs" 5 | "github.com/metagogs/gogs/acceptor" 6 | "github.com/metagogs/gogs/config" 7 | "github.com/metagogs/test/internal/server" 8 | "github.com/metagogs/test/internal/svc" 9 | "github.com/metagogs/test/model" 10 | ) 11 | 12 | func main() { 13 | 14 | config := config.NewConfig() 15 | 16 | app := gogs.NewApp(config) 17 | app.AddAcceptor(acceptor.NewWSAcceptor(&acceptor.AcceptorConfig{ 18 | HttpPort: 8888, 19 | Name: "base", 20 | Groups: []*acceptor.AcceptorGroupConfig{ 21 | &acceptor.AcceptorGroupConfig{ 22 | GroupName: "base", 23 | }, 24 | }, 25 | })) 26 | 27 | app.AddAcceptor(acceptor.NewWebRTCAcceptor(&acceptor.AcceptorConfig{ 28 | HttpPort: 8889, 29 | UdpPort: 9000, 30 | Name: "world", 31 | Groups: []*acceptor.AcceptorGroupConfig{ 32 | &acceptor.AcceptorGroupConfig{ 33 | GroupName: "data", 34 | }, 35 | }, 36 | })) 37 | 38 | ctx := svc.NewServiceContext(app) 39 | srv := server.NewServer(ctx) 40 | 41 | model.RegisterAllComponents(app, srv) 42 | 43 | defer app.Shutdown() 44 | app.Start() 45 | } 46 | -------------------------------------------------------------------------------- /tools/gogs/docker/gen.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "github.com/metagogs/gogs/tools/gogs/docker/gentemplate" 5 | "github.com/metagogs/gogs/utils/templatex" 6 | "github.com/pterm/pterm" 7 | ) 8 | 9 | type DockerGen struct { 10 | cnproxy bool 11 | } 12 | 13 | func NewDockerGen(cnproxy bool) (*DockerGen, error) { 14 | return &DockerGen{ 15 | cnproxy: cnproxy, 16 | }, nil 17 | } 18 | 19 | func (g *DockerGen) Generate() error { 20 | 21 | if err := g.docker(); err != nil { 22 | return err 23 | } 24 | 25 | return nil 26 | } 27 | 28 | func (g *DockerGen) docker() error { 29 | data := map[string]interface{}{} 30 | data["Proxy"] = g.cnproxy 31 | 32 | if err := templatex.With("gogs").Parse(gentemplate.DockerTpl).SaveTo(data, "Dockerfile", true); err != nil { 33 | pterm.Error.Printfln("generate file error Dockerfile:" + err.Error()) 34 | return err 35 | } 36 | 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /tools/gogs/docker/gentemplate/docker.tpl: -------------------------------------------------------------------------------- 1 | FROM golang:1.21-alpine AS builder 2 | WORKDIR /app 3 | 4 | ENV GOOS linux 5 | RUN go env -w GO111MODULE=on 6 | {{if .Proxy}} 7 | RUN go env -w GOPROXY=https://goproxy.cn,direct 8 | {{end}} 9 | ADD go.mod . 10 | ADD go.sum . 11 | RUN go mod download 12 | 13 | COPY . . 14 | RUN go build -ldflags="-s -w" -o app main.go 15 | 16 | FROM alpine 17 | 18 | WORKDIR /app 19 | COPY --from=builder /app/app /app/app 20 | COPY --from=builder /app/config.yaml /app/config.yaml 21 | 22 | CMD ["./app"] 23 | -------------------------------------------------------------------------------- /tools/gogs/docker/gentemplate/template.go: -------------------------------------------------------------------------------- 1 | package gentemplate 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | //go:embed docker.tpl 8 | var DockerTpl string 9 | -------------------------------------------------------------------------------- /tools/gogs/gen/gen_test.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/metagogs/gogs/utils/filex" 8 | ) 9 | 10 | func TestNewGen(t *testing.T) { 11 | type args struct { 12 | proto string 13 | basePackage string 14 | } 15 | tests := []struct { 16 | name string 17 | args args 18 | wantErr bool 19 | }{ 20 | { 21 | name: "new gen", 22 | args: args{ 23 | proto: "testdata/data.proto", 24 | basePackage: "github.com/metagogs/test", 25 | }, 26 | wantErr: false, 27 | }, 28 | } 29 | for _, tt := range tests { 30 | t.Run(tt.name, func(t *testing.T) { 31 | g, err := NewGen(tt.args.proto, tt.args.basePackage) 32 | g.Home = "test/" 33 | g.debugNoPb = true 34 | if (err != nil) != tt.wantErr { 35 | t.Errorf("NewGen() error = %v, wantErr %v", err, tt.wantErr) 36 | return 37 | } 38 | err = g.Generate() 39 | if (err != nil) != tt.wantErr { 40 | t.Errorf("NewGen() error = %v, wantErr %v", err, tt.wantErr) 41 | return 42 | } 43 | var haveErr bool 44 | if ok := filex.IsFileEqual("testdata/main.go", g.getAppFile()); !ok { 45 | t.Errorf("Init.Generate() error = %s is not equal to %s", g.getAppFile(), 46 | "testdata/main.go") 47 | haveErr = true 48 | } 49 | if ok := filex.IsFileEqual("testdata/model/data.ep.go", g.getEPFile()); !ok { 50 | t.Errorf("Init.Generate() error = %s is not equal to %s", g.getEPFile(), 51 | "testdata/data.ep.go") 52 | haveErr = true 53 | } 54 | if ok := filex.IsFileEqual("testdata/internal/svc/service_context.go", g.getSvcFile()); !ok { 55 | t.Errorf("Init.Generate() error = %s is not equal to %s", g.getSvcFile(), 56 | "testdata/internal/svc/service_context.go") 57 | haveErr = true 58 | } 59 | if ok := filex.IsFileEqual("testdata/internal/server/server.go", g.getServerFile()); !ok { 60 | t.Errorf("Init.Generate() error = %s is not equal to %s", g.getServerFile(), 61 | "testdata/internal/server/server.go") 62 | haveErr = true 63 | } 64 | if ok := filex.IsFileEqual("testdata/internal/message/message.go", g.getMessageFile()); !ok { 65 | t.Errorf("Init.Generate() error = %s is not equal to %s", g.getMessageFile(), 66 | "testdata/internal/message/message.go") 67 | haveErr = true 68 | } 69 | if ok := filex.IsFileEqual("testdata/internal/logic/baseworld/bind_user_logic.go", g.getLogicFile()[0]); !ok { 70 | t.Errorf("Init.Generate() error = %s is not equal to %s", g.getLogicFile()[0], 71 | "testdata/internal/logic/baseworld/bind_user_logic.go") 72 | haveErr = true 73 | } 74 | 75 | if !haveErr { 76 | g.clean() 77 | _ = os.RemoveAll("test") 78 | } 79 | }) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tools/gogs/gen/gentemplate/app.tpl: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "{{.BasePackage}}/{{.Package}}" 5 | "{{.BasePackage}}/internal/server" 6 | "{{.BasePackage}}/internal/svc" 7 | "github.com/metagogs/gogs" 8 | "github.com/metagogs/gogs/acceptor" 9 | "github.com/metagogs/gogs/config" 10 | ) 11 | 12 | func main() { 13 | 14 | config := config.NewConfig() 15 | 16 | app := gogs.NewApp(config) 17 | app.AddAcceptor(acceptor.NewWSAcceptor(&acceptor.AcceptorConfig{ 18 | HttpPort: 8888, 19 | Name: "base", 20 | Groups: []*acceptor.AcceptorGroupConfig{ 21 | &acceptor.AcceptorGroupConfig{ 22 | GroupName: "base", 23 | }, 24 | }, 25 | })) 26 | 27 | app.AddAcceptor(acceptor.NewWebRTCAcceptor(&acceptor.AcceptorConfig{ 28 | HttpPort: 8889, 29 | UdpPort: 9000, 30 | Name: "world", 31 | Groups: []*acceptor.AcceptorGroupConfig{ 32 | &acceptor.AcceptorGroupConfig{ 33 | GroupName: "data", 34 | }, 35 | }, 36 | })) 37 | 38 | ctx := svc.NewServiceContext(app) 39 | srv := server.NewServer(ctx) 40 | 41 | {{.Package}}.RegisterAllComponents(app, srv) 42 | 43 | defer app.Shutdown() 44 | app.Start() 45 | } 46 | -------------------------------------------------------------------------------- /tools/gogs/gen/gentemplate/config.tpl: -------------------------------------------------------------------------------- 1 | Debug: true -------------------------------------------------------------------------------- /tools/gogs/gen/gentemplate/ep.go.tpl: -------------------------------------------------------------------------------- 1 | // Code generated by gogs. DO NOT EDIT. 2 | package {{.Package}} 3 | 4 | import ( 5 | "context" 6 | "reflect" 7 | 8 | "github.com/metagogs/gogs" 9 | "github.com/metagogs/gogs/component" 10 | "github.com/metagogs/gogs/packet" 11 | "github.com/metagogs/gogs/session" 12 | ) 13 | 14 | func RegisterAllComponents(s *gogs.App, srv Component) { 15 | {{range .Components}} register{{.Name}}Component(s, srv) 16 | {{end}} 17 | } 18 | 19 | {{range .Components}} 20 | func register{{.Name}}Component(s *gogs.App, srv Component) { 21 | s.RegisterComponent(_{{.Name}}ComponentDesc, srv) 22 | } 23 | {{end}} 24 | 25 | type Component interface { 26 | {{range .Components}}{{range .Fields}} 27 | {{if not .ServerMessage}}{{.Name}}(ctx context.Context, s *session.Session, in *{{.Name}}) 28 | {{end}} 29 | {{end}}{{end}} 30 | } 31 | 32 | {{range .Components}}{{range .Fields}} 33 | {{if not .ServerMessage}} 34 | func _{{.ComponentName}}Component_{{.Name}}_Handler(srv interface{}, ctx context.Context, sess *session.Session, in interface{}) { 35 | srv.(Component).{{.Name}}(ctx, sess, in.(*{{.Name}})) 36 | } 37 | {{end}} 38 | {{end}}{{end}} 39 | 40 | {{range .Components}} 41 | var _{{.Name}}ComponentDesc = component.ComponentDesc{ 42 | ComponentName: "{{.Name}}Component", 43 | ComponentIndex: {{.Index}}, // equal to module index 44 | ComponentType: (*Component)(nil), 45 | Methods: []component.ComponentMethodDesc{ 46 | {{range .Fields}}{ 47 | MethodIndex: packet.CreateAction(packet.ServicePacket, {{.ComponentIndex}}, {{.Index}}), // {{.Action16}} {{.Action10}} 48 | FieldType: reflect.TypeOf({{.Name}}{}), 49 | {{if .ServerMessage}}Handler: nil,{{else}}Handler: _{{.ComponentName}}Component_{{.Name}}_Handler,{{end}} 50 | FieldHandler: func() interface{} { 51 | return new({{.Name}}) 52 | }, 53 | }, 54 | {{end}} 55 | }, 56 | } 57 | {{end}} 58 | -------------------------------------------------------------------------------- /tools/gogs/gen/gentemplate/go.mod.tpl: -------------------------------------------------------------------------------- 1 | module {{.ProjectPackage}} 2 | 3 | {{.GoVersion}} 4 | 5 | require ( 6 | github.com/metagogs/gogs {{.GoGSVersion}} 7 | go.uber.org/zap v1.23.0 8 | google.golang.org/protobuf v1.28.1 9 | ) -------------------------------------------------------------------------------- /tools/gogs/gen/gentemplate/logic.tpl: -------------------------------------------------------------------------------- 1 | package {{.LogicPackage}} 2 | 3 | import ( 4 | "context" 5 | 6 | "{{.BasePackage}}/{{.Package}}" 7 | "{{.BasePackage}}/internal/svc" 8 | "github.com/metagogs/gogs/gslog" 9 | "github.com/metagogs/gogs/session" 10 | "go.uber.org/zap" 11 | ) 12 | 13 | type {{.Name}}Logic struct { 14 | ctx context.Context 15 | svcCtx *svc.ServiceContext 16 | session *session.Session 17 | *zap.Logger 18 | } 19 | 20 | func New{{.Name}}Logic(ctx context.Context, svcCtx *svc.ServiceContext, sess *session.Session) *{{.Name}}Logic { 21 | return &{{.Name}}Logic{ 22 | ctx: ctx, 23 | svcCtx: svcCtx, 24 | session: sess, 25 | Logger: gslog.NewLog("{{.SnakeName}}_logic"), 26 | } 27 | } 28 | 29 | func (l *{{.Name}}Logic) Handler(in *{{.Package}}.{{.Name}}) { 30 | 31 | } 32 | -------------------------------------------------------------------------------- /tools/gogs/gen/gentemplate/message.tpl: -------------------------------------------------------------------------------- 1 | // Code generated by gogs. DO NOT EDIT. 2 | package message 3 | 4 | import ( 5 | "{{.BasePackage}}/{{.Package}}" 6 | "github.com/metagogs/gogs/session" 7 | ) 8 | 9 | {{range .Components}} 10 | {{range .Fields}} 11 | 12 | func Send{{.Name}}(s *session.Session, in *{{.Package}}.{{.Name}}) error { 13 | return s.SendMessage(in, "{{.Name}}") 14 | } 15 | 16 | {{end}} 17 | {{end}} 18 | -------------------------------------------------------------------------------- /tools/gogs/gen/gentemplate/proto.tpl: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package model; 4 | option go_package = "/model"; 5 | 6 | // the Main Components 7 | // only have one, include some child componetns 8 | // all the message in this struct is the componetns 9 | // @gogs:Components 10 | message Components { 11 | // don't care the filed name, we never use it 12 | // but you should be careful about the filed number 13 | BaseWorld BaseWorld = 1; 14 | } 15 | 16 | // componetns, 17 | // all the messages are used for communication between the client and the server 18 | message BaseWorld { 19 | // don't care the filed name, we never use it 20 | // but you should be careful about the filed number 21 | BindUser BindUser = 1; 22 | 23 | BindSuccess BindSuccess = 2; 24 | } 25 | // common message 26 | // the message is used for the client and the server to communicate 27 | // the corresponding method will be generated according to this message 28 | message BindUser { 29 | string uid = 1; 30 | } 31 | 32 | 33 | // it is only used for messages sent from the server to the client 34 | // the server will not receive the message and will not generate the corresponding method 35 | // @gogs:ServerMessage 36 | message BindSuccess { 37 | } -------------------------------------------------------------------------------- /tools/gogs/gen/gentemplate/server.tpl: -------------------------------------------------------------------------------- 1 | // Code generated by gogs. DO NOT EDIT. 2 | package server 3 | 4 | import ( 5 | "context" 6 | 7 | "{{.BasePackage}}/{{.Package}}" 8 | {{range .Components}} "{{.BasePackage}}/internal/logic/{{.Name | ToLower}}" 9 | {{end}} 10 | "{{.BasePackage}}/internal/svc" 11 | "github.com/metagogs/gogs/session" 12 | ) 13 | 14 | type Server struct { 15 | svcCtx *svc.ServiceContext 16 | } 17 | 18 | func NewServer(svcCtx *svc.ServiceContext) *Server { 19 | return &Server{ 20 | svcCtx: svcCtx, 21 | } 22 | } 23 | 24 | 25 | 26 | {{range .Components}} 27 | {{range .Fields}} 28 | 29 | {{if not .ServerMessage}} 30 | func (gogs *Server) {{.Name}}(ctx context.Context, s *session.Session, in *{{.Package}}.{{.Name}}) { 31 | l := {{ .ComponentName | ToLower }}.New{{.Name}}Logic(ctx, gogs.svcCtx, s) 32 | l.Handler(in) 33 | } 34 | {{end}} 35 | 36 | {{end}} 37 | {{end}} 38 | 39 | 40 | -------------------------------------------------------------------------------- /tools/gogs/gen/gentemplate/svc.tpl: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "github.com/metagogs/gogs" 5 | ) 6 | 7 | type ServiceContext struct { 8 | *gogs.App 9 | } 10 | 11 | func NewServiceContext(app *gogs.App) *ServiceContext { 12 | return &ServiceContext{ 13 | App: app, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tools/gogs/gen/gentemplate/template.go: -------------------------------------------------------------------------------- 1 | package gentemplate 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | //go:embed app.tpl 8 | var AppTpl string 9 | 10 | //go:embed ep.go.tpl 11 | var EPTpl string 12 | 13 | //go:embed logic.tpl 14 | var LogicTpl string 15 | 16 | //go:embed server.tpl 17 | var ServerTpl string 18 | 19 | //go:embed message.tpl 20 | var MessageTpl string 21 | 22 | //go:embed svc.tpl 23 | var SvcTpl string 24 | 25 | //go:embed go.mod.tpl 26 | var GoModTpl string 27 | 28 | //go:embed proto.tpl 29 | var ProtoTpl string 30 | 31 | //go:embed config.tpl 32 | var ConfigTpl string 33 | -------------------------------------------------------------------------------- /tools/gogs/gen/init.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | "strings" 7 | 8 | "github.com/metagogs/gogs" 9 | "github.com/metagogs/gogs/tools/gogs/gen/gentemplate" 10 | "github.com/metagogs/gogs/utils/templatex" 11 | "github.com/pterm/pterm" 12 | ) 13 | 14 | type Init struct { 15 | PackageName string 16 | Home string 17 | } 18 | 19 | func NewInit(name string) *Init { 20 | return &Init{ 21 | PackageName: name, 22 | } 23 | } 24 | 25 | func (g *Init) Generate() error { 26 | if err := g.goMod(); err != nil { 27 | return err 28 | } 29 | if err := g.config(); err != nil { 30 | return err 31 | } 32 | if err := g.proto(); err != nil { 33 | return err 34 | } 35 | 36 | return nil 37 | } 38 | 39 | func (g *Init) goMod() error { 40 | file := g.getGoModFile() 41 | data := map[string]string{ 42 | "ProjectPackage": g.PackageName, 43 | "GoVersion": strings.ReplaceAll(runtime.Version(), "go", "go "), 44 | "GoGSVersion": gogs.Version, 45 | } 46 | if err := templatex.With("gogs").GoFmt(false).Parse(gentemplate.GoModTpl).SaveTo(data, file, false); err != nil { 47 | pterm.Error.Printfln("generate file error " + file + ":" + err.Error()) 48 | return err 49 | } 50 | 51 | return nil 52 | } 53 | 54 | func (g *Init) config() error { 55 | file := g.getConfigFile() 56 | if err := templatex.With("gogs").GoFmt(false).Parse(gentemplate.ConfigTpl).SaveTo(nil, file, false); err != nil { 57 | pterm.Error.Printfln("generate file error " + file + ":" + err.Error()) 58 | return err 59 | } 60 | 61 | return nil 62 | } 63 | 64 | func (g *Init) proto() error { 65 | file := g.getProtoFile() 66 | if err := templatex.With("gogs").GoFmt(false).Parse(gentemplate.ProtoTpl).SaveTo(nil, file, false); err != nil { 67 | pterm.Error.Printfln("generate file error " + file + ":" + err.Error()) 68 | return err 69 | } 70 | 71 | return nil 72 | } 73 | 74 | func (g *Init) getGoModFile() string { 75 | return g.Home + "go.mod" 76 | } 77 | 78 | func (g *Init) getConfigFile() string { 79 | return g.Home + "config.yaml" 80 | } 81 | 82 | func (g *Init) getProtoFile() string { 83 | return g.Home + "data.proto" 84 | } 85 | 86 | func (g *Init) clean() { 87 | _ = os.Remove(g.getGoModFile()) 88 | _ = os.Remove(g.getConfigFile()) 89 | _ = os.Remove(g.getProtoFile()) 90 | } 91 | -------------------------------------------------------------------------------- /tools/gogs/gen/init_test.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/metagogs/gogs/utils/filex" 8 | ) 9 | 10 | func TestInit_Generate(t *testing.T) { 11 | type fields struct { 12 | PackageName string 13 | } 14 | tests := []struct { 15 | name string 16 | fields fields 17 | wantErr bool 18 | }{ 19 | { 20 | name: "generate", 21 | fields: fields{ 22 | PackageName: "github.com/metagogs/test", 23 | }, 24 | wantErr: false, 25 | }, 26 | } 27 | for _, tt := range tests { 28 | t.Run(tt.name, func(t *testing.T) { 29 | g := &Init{ 30 | PackageName: tt.fields.PackageName, 31 | } 32 | g.Home = "test/" 33 | if err := g.Generate(); (err != nil) != tt.wantErr { 34 | t.Errorf("Init.Generate() error = %v, wantErr %v", err, tt.wantErr) 35 | } 36 | var haveErr bool 37 | 38 | if ok := filex.IsFileEqual("testdata/config.yaml", g.getConfigFile()); !ok { 39 | t.Errorf("Init.Generate() error = %s is not equal to %s", g.getGoModFile(), "testdata/config.yaml") 40 | haveErr = true 41 | } 42 | if ok := filex.IsFileEqual("testdata/data.proto", g.getProtoFile()); !ok { 43 | t.Errorf("Init.Generate() error = %s is not equal to %s", g.getProtoFile(), "testdata/data.proto") 44 | haveErr = true 45 | } 46 | 47 | if !haveErr { 48 | g.clean() 49 | _ = os.Remove("test") 50 | } 51 | 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tools/gogs/gen/testdata/config.yaml: -------------------------------------------------------------------------------- 1 | Debug: true -------------------------------------------------------------------------------- /tools/gogs/gen/testdata/data.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package model; 4 | option go_package = "/model"; 5 | 6 | // the Main Components 7 | // only have one, include some child componetns 8 | // all the message in this struct is the componetns 9 | // @gogs:Components 10 | message Components { 11 | // don't care the filed name, we never use it 12 | // but you should be careful about the filed number 13 | BaseWorld BaseWorld = 1; 14 | } 15 | 16 | // componetns, 17 | // all the messages are used for communication between the client and the server 18 | message BaseWorld { 19 | // don't care the filed name, we never use it 20 | // but you should be careful about the filed number 21 | BindUser BindUser = 1; 22 | 23 | BindSuccess BindSuccess = 2; 24 | } 25 | // common message 26 | // the message is used for the client and the server to communicate 27 | // the corresponding method will be generated according to this message 28 | message BindUser { 29 | string uid = 1; 30 | } 31 | 32 | 33 | // it is only used for messages sent from the server to the client 34 | // the server will not receive the message and will not generate the corresponding method 35 | // @gogs:ServerMessage 36 | message BindSuccess { 37 | } -------------------------------------------------------------------------------- /tools/gogs/gen/testdata/internal/logic/baseworld/bind_user_logic.go: -------------------------------------------------------------------------------- 1 | package baseworld 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/metagogs/gogs/gslog" 7 | "github.com/metagogs/gogs/session" 8 | "github.com/metagogs/test/internal/svc" 9 | "github.com/metagogs/test/model" 10 | "go.uber.org/zap" 11 | ) 12 | 13 | type BindUserLogic struct { 14 | ctx context.Context 15 | svcCtx *svc.ServiceContext 16 | session *session.Session 17 | *zap.Logger 18 | } 19 | 20 | func NewBindUserLogic(ctx context.Context, svcCtx *svc.ServiceContext, sess *session.Session) *BindUserLogic { 21 | return &BindUserLogic{ 22 | ctx: ctx, 23 | svcCtx: svcCtx, 24 | session: sess, 25 | Logger: gslog.NewLog("bind_user_logic"), 26 | } 27 | } 28 | 29 | func (l *BindUserLogic) Handler(in *model.BindUser) { 30 | 31 | } 32 | -------------------------------------------------------------------------------- /tools/gogs/gen/testdata/internal/message/message.go: -------------------------------------------------------------------------------- 1 | // Code generated by gogs. DO NOT EDIT. 2 | package message 3 | 4 | import ( 5 | "github.com/metagogs/gogs/session" 6 | "github.com/metagogs/test/model" 7 | ) 8 | 9 | func SendBindUser(s *session.Session, in *model.BindUser) error { 10 | return s.SendMessage(in, "BindUser") 11 | } 12 | 13 | func SendBindSuccess(s *session.Session, in *model.BindSuccess) error { 14 | return s.SendMessage(in, "BindSuccess") 15 | } 16 | -------------------------------------------------------------------------------- /tools/gogs/gen/testdata/internal/server/server.go: -------------------------------------------------------------------------------- 1 | // Code generated by gogs. DO NOT EDIT. 2 | package server 3 | 4 | import ( 5 | "context" 6 | 7 | "github.com/metagogs/test/internal/logic/baseworld" 8 | "github.com/metagogs/test/model" 9 | 10 | "github.com/metagogs/gogs/session" 11 | "github.com/metagogs/test/internal/svc" 12 | ) 13 | 14 | type Server struct { 15 | svcCtx *svc.ServiceContext 16 | } 17 | 18 | func NewServer(svcCtx *svc.ServiceContext) *Server { 19 | return &Server{ 20 | svcCtx: svcCtx, 21 | } 22 | } 23 | 24 | func (gogs *Server) BindUser(ctx context.Context, s *session.Session, in *model.BindUser) { 25 | l := baseworld.NewBindUserLogic(ctx, gogs.svcCtx, s) 26 | l.Handler(in) 27 | } 28 | -------------------------------------------------------------------------------- /tools/gogs/gen/testdata/internal/svc/service_context.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "github.com/metagogs/gogs" 5 | ) 6 | 7 | type ServiceContext struct { 8 | *gogs.App 9 | } 10 | 11 | func NewServiceContext(app *gogs.App) *ServiceContext { 12 | return &ServiceContext{ 13 | App: app, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tools/gogs/gen/testdata/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/metagogs/gogs" 5 | "github.com/metagogs/gogs/acceptor" 6 | "github.com/metagogs/gogs/config" 7 | "github.com/metagogs/test/internal/server" 8 | "github.com/metagogs/test/internal/svc" 9 | "github.com/metagogs/test/model" 10 | ) 11 | 12 | func main() { 13 | 14 | config := config.NewConfig() 15 | 16 | app := gogs.NewApp(config) 17 | app.AddAcceptor(acceptor.NewWSAcceptor(&acceptor.AcceptorConfig{ 18 | HttpPort: 8888, 19 | Name: "base", 20 | Groups: []*acceptor.AcceptorGroupConfig{ 21 | &acceptor.AcceptorGroupConfig{ 22 | GroupName: "base", 23 | }, 24 | }, 25 | })) 26 | 27 | app.AddAcceptor(acceptor.NewWebRTCAcceptor(&acceptor.AcceptorConfig{ 28 | HttpPort: 8889, 29 | UdpPort: 9000, 30 | Name: "world", 31 | Groups: []*acceptor.AcceptorGroupConfig{ 32 | &acceptor.AcceptorGroupConfig{ 33 | GroupName: "data", 34 | }, 35 | }, 36 | })) 37 | 38 | ctx := svc.NewServiceContext(app) 39 | srv := server.NewServer(ctx) 40 | 41 | model.RegisterAllComponents(app, srv) 42 | 43 | defer app.Shutdown() 44 | app.Start() 45 | } 46 | -------------------------------------------------------------------------------- /tools/gogs/gen/testdata/model/data.ep.go: -------------------------------------------------------------------------------- 1 | // Code generated by gogs. DO NOT EDIT. 2 | package model 3 | 4 | import ( 5 | "context" 6 | "reflect" 7 | 8 | "github.com/metagogs/gogs" 9 | "github.com/metagogs/gogs/component" 10 | "github.com/metagogs/gogs/packet" 11 | "github.com/metagogs/gogs/session" 12 | ) 13 | 14 | func RegisterAllComponents(s *gogs.App, srv Component) { 15 | registerBaseWorldComponent(s, srv) 16 | 17 | } 18 | 19 | func registerBaseWorldComponent(s *gogs.App, srv Component) { 20 | s.RegisterComponent(_BaseWorldComponentDesc, srv) 21 | } 22 | 23 | type Component interface { 24 | BindUser(ctx context.Context, s *session.Session, in *BindUser) 25 | } 26 | 27 | func _BaseWorldComponent_BindUser_Handler(srv interface{}, ctx context.Context, sess *session.Session, in interface{}) { 28 | srv.(Component).BindUser(ctx, sess, in.(*BindUser)) 29 | } 30 | 31 | var _BaseWorldComponentDesc = component.ComponentDesc{ 32 | ComponentName: "BaseWorldComponent", 33 | ComponentIndex: 1, // equal to module index 34 | ComponentType: (*Component)(nil), 35 | Methods: []component.ComponentMethodDesc{ 36 | { 37 | MethodIndex: packet.CreateAction(packet.ServicePacket, 1, 1), // 0x810001 8454145 38 | FieldType: reflect.TypeOf(BindUser{}), 39 | Handler: _BaseWorldComponent_BindUser_Handler, 40 | FieldHandler: func() interface{} { 41 | return new(BindUser) 42 | }, 43 | }, 44 | { 45 | MethodIndex: packet.CreateAction(packet.ServicePacket, 1, 2), // 0x810002 8454146 46 | FieldType: reflect.TypeOf(BindSuccess{}), 47 | Handler: nil, 48 | FieldHandler: func() interface{} { 49 | return new(BindSuccess) 50 | }, 51 | }, 52 | }, 53 | } 54 | -------------------------------------------------------------------------------- /tools/gogs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/metagogs/gogs" 7 | "github.com/metagogs/gogs/tools/gogs/cmd" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func init() { 12 | cmd.RootCmd.AddCommand(versionCmd) 13 | } 14 | 15 | var versionCmd = &cobra.Command{ 16 | Use: "version", 17 | Short: "show version", 18 | Long: `show the gogs version`, 19 | Run: func(cmd *cobra.Command, args []string) { 20 | fmt.Println(gogs.Version) 21 | }, 22 | } 23 | 24 | func main() { 25 | cmd.Execute() 26 | } 27 | -------------------------------------------------------------------------------- /tools/gogs/protoparse/comments.go: -------------------------------------------------------------------------------- 1 | package protoparse 2 | 3 | import "strings" 4 | 5 | func CommentsContains(lines []string, target string) bool { 6 | for _, line := range lines { 7 | if strings.Contains(line, target) { 8 | return true 9 | } 10 | } 11 | 12 | return false 13 | } 14 | -------------------------------------------------------------------------------- /tools/gogs/protoparse/parse.go: -------------------------------------------------------------------------------- 1 | package protoparse 2 | 3 | import ( 4 | "go/token" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | "unicode" 9 | "unicode/utf8" 10 | 11 | "github.com/emicklei/proto" 12 | ) 13 | 14 | type Package struct { 15 | *proto.Package 16 | } 17 | 18 | type Import struct { 19 | *proto.Import 20 | } 21 | 22 | type Message struct { 23 | *proto.Message 24 | } 25 | 26 | type Service struct { 27 | *proto.Service 28 | RPC []*RPC 29 | } 30 | 31 | type RPC struct { 32 | *proto.RPC 33 | } 34 | 35 | type Proto struct { 36 | Src string 37 | Name string 38 | Package Package 39 | PbPackage string 40 | GoPackage string 41 | Import []Import 42 | Message []Message 43 | Service Service 44 | } 45 | type ProtoParser struct{} 46 | 47 | func NewProtoParser() *ProtoParser { 48 | return &ProtoParser{} 49 | } 50 | 51 | func (p *ProtoParser) Parse(src string) (Proto, error) { 52 | var ret Proto 53 | 54 | abs, err := filepath.Abs(src) 55 | if err != nil { 56 | return Proto{}, err 57 | } 58 | 59 | r, err := os.Open(abs) 60 | if err != nil { 61 | return ret, err 62 | } 63 | defer r.Close() 64 | 65 | parser := proto.NewParser(r) 66 | set, err := parser.Parse() 67 | if err != nil { 68 | return ret, err 69 | } 70 | 71 | var serviceList []Service 72 | proto.Walk( 73 | set, 74 | proto.WithImport(func(i *proto.Import) { 75 | ret.Import = append(ret.Import, Import{Import: i}) 76 | }), 77 | proto.WithMessage(func(message *proto.Message) { 78 | ret.Message = append(ret.Message, Message{Message: message}) 79 | }), 80 | proto.WithPackage(func(p *proto.Package) { 81 | ret.Package = Package{Package: p} 82 | }), 83 | proto.WithService(func(service *proto.Service) { 84 | serv := Service{Service: service} 85 | elements := service.Elements 86 | for _, el := range elements { 87 | v, _ := el.(*proto.RPC) 88 | if v == nil { 89 | continue 90 | } 91 | serv.RPC = append(serv.RPC, &RPC{RPC: v}) 92 | } 93 | 94 | serviceList = append(serviceList, serv) 95 | }), 96 | proto.WithOption(func(option *proto.Option) { 97 | if option.Name == "go_package" { 98 | ret.GoPackage = option.Constant.Source 99 | } 100 | }), 101 | ) 102 | 103 | name := filepath.Base(abs) 104 | 105 | if len(ret.GoPackage) == 0 { 106 | ret.GoPackage = ret.Package.Name 107 | } 108 | ret.PbPackage = GoSanitized(filepath.Base(ret.GoPackage)) 109 | ret.Src = abs 110 | ret.Name = name 111 | if len(serviceList) > 0 { 112 | ret.Service = serviceList[0] 113 | } 114 | 115 | return ret, nil 116 | } 117 | 118 | func GoSanitized(s string) string { 119 | // Sanitize the input to the set of valid characters, 120 | // which must be '_' or be in the Unicode L or N categories. 121 | s = strings.Map(func(r rune) rune { 122 | if unicode.IsLetter(r) || unicode.IsDigit(r) { 123 | return r 124 | } 125 | return '_' 126 | }, s) 127 | 128 | // Prepend '_' in the event of a Go keyword conflict or if 129 | // the identifier is invalid (does not start in the Unicode L category). 130 | r, _ := utf8.DecodeRuneInString(s) 131 | if token.Lookup(s).IsKeyword() || !unicode.IsLetter(r) { 132 | return "_" + s 133 | } 134 | return s 135 | } 136 | -------------------------------------------------------------------------------- /tools/gogs/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | go build -o gogs main.go 4 | chmod +x gogs 5 | cp gogs `go env GOPATH`/bin 6 | rm gogs 7 | 8 | -------------------------------------------------------------------------------- /unity/Protobuf/Google.Protobuf.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metagogs/gogs/f1a44c5879cb63074d315a4fefbdb269267bcff7/unity/Protobuf/Google.Protobuf.dll -------------------------------------------------------------------------------- /unity/Protobuf/System.Buffers.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metagogs/gogs/f1a44c5879cb63074d315a4fefbdb269267bcff7/unity/Protobuf/System.Buffers.dll -------------------------------------------------------------------------------- /unity/Protobuf/System.Memory.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metagogs/gogs/f1a44c5879cb63074d315a4fefbdb269267bcff7/unity/Protobuf/System.Memory.dll -------------------------------------------------------------------------------- /unity/Protobuf/System.Runtime.CompilerServices.Unsafe.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metagogs/gogs/f1a44c5879cb63074d315a4fefbdb269267bcff7/unity/Protobuf/System.Runtime.CompilerServices.Unsafe.dll -------------------------------------------------------------------------------- /unity/WebSocket/endel.nativewebsocket.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "endel.nativewebsocket" 3 | } 4 | -------------------------------------------------------------------------------- /utils/bytebuffer/benchmark_test.go: -------------------------------------------------------------------------------- 1 | package bytebuffer 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "github.com/metagogs/gogs/utils/randstr" 8 | ) 9 | 10 | //go test -bench=. -benchmem -count=5 11 | 12 | func BenchmarkPool(b *testing.B) { 13 | b.ResetTimer() 14 | 15 | for i := 0; i < b.N; i++ { 16 | generateData, _ := randStr() 17 | generateData2, _ := randStr() 18 | bb := defaultPool.Get() 19 | bb.Write(generateData) 20 | bb.Write(generateData2) 21 | defaultPool.Put(bb) 22 | } 23 | } 24 | 25 | func BenchmarkNormal(b *testing.B) { 26 | b.ResetTimer() 27 | 28 | for i := 0; i < b.N; i++ { 29 | generateData, length := randStr() 30 | generateData2, length2 := randStr() 31 | data := make([]byte, length+length2) 32 | copy(data, generateData) 33 | copy(data[length:], generateData2) 34 | } 35 | 36 | } 37 | 38 | func randStr() ([]byte, int) { 39 | res := randstr.RandStr(100 + rand.Intn(100)) 40 | data := []byte(res) 41 | return data, len(data) 42 | } 43 | -------------------------------------------------------------------------------- /utils/bytebuffer/bytebuffer.go: -------------------------------------------------------------------------------- 1 | package bytebuffer 2 | 3 | import "io" 4 | 5 | // ByteBuffer provides byte buffer, which can be used for minimizing 6 | // memory allocations. 7 | // 8 | // ByteBuffer may be used with functions appending data to the given []byte 9 | // slice. See example code for details. 10 | // 11 | // Use Get for obtaining an empty byte buffer. 12 | type ByteBuffer struct { 13 | 14 | // B is a byte buffer to use in append-like workloads. 15 | // See example code for details. 16 | B []byte 17 | } 18 | 19 | // Len returns the size of the byte buffer. 20 | func (b *ByteBuffer) Len() int { 21 | return len(b.B) 22 | } 23 | 24 | // ReadFrom implements io.ReaderFrom. 25 | // 26 | // The function appends all the data read from r to b. 27 | func (b *ByteBuffer) ReadFrom(r io.Reader) (int64, error) { 28 | p := b.B 29 | nStart := int64(len(p)) 30 | nMax := int64(cap(p)) 31 | n := nStart 32 | if nMax == 0 { 33 | nMax = 64 34 | p = make([]byte, nMax) 35 | } else { 36 | p = p[:nMax] 37 | } 38 | for { 39 | if n == nMax { 40 | nMax *= 2 41 | bNew := make([]byte, nMax) 42 | copy(bNew, p) 43 | p = bNew 44 | } 45 | nn, err := r.Read(p[n:]) 46 | n += int64(nn) 47 | if err != nil { 48 | b.B = p[:n] 49 | n -= nStart 50 | if err == io.EOF { 51 | return n, nil 52 | } 53 | return n, err 54 | } 55 | } 56 | } 57 | 58 | // WriteTo implements io.WriterTo. 59 | func (b *ByteBuffer) WriteTo(w io.Writer) (int64, error) { 60 | n, err := w.Write(b.B) 61 | return int64(n), err 62 | } 63 | 64 | // Bytes returns b.B, i.e. all the bytes accumulated in the buffer. 65 | // 66 | // The purpose of this function is bytes.Buffer compatibility. 67 | func (b *ByteBuffer) Bytes() []byte { 68 | return b.B 69 | } 70 | 71 | // Write implements io.Writer - it appends p to ByteBuffer.B 72 | func (b *ByteBuffer) Write(p []byte) (int, error) { 73 | b.B = append(b.B, p...) 74 | return len(p), nil 75 | } 76 | 77 | // WriteByte appends the byte c to the buffer. 78 | // 79 | // The purpose of this function is bytes.Buffer compatibility. 80 | // 81 | // The function always returns nil. 82 | func (b *ByteBuffer) WriteByte(c byte) error { 83 | b.B = append(b.B, c) 84 | return nil 85 | } 86 | 87 | // WriteString appends s to ByteBuffer.B. 88 | func (b *ByteBuffer) WriteString(s string) (int, error) { 89 | b.B = append(b.B, s...) 90 | return len(s), nil 91 | } 92 | 93 | // Set sets ByteBuffer.B to p. 94 | func (b *ByteBuffer) Set(p []byte) { 95 | b.B = append(b.B[:0], p...) 96 | } 97 | 98 | // SetString sets ByteBuffer.B to s. 99 | func (b *ByteBuffer) SetString(s string) { 100 | b.B = append(b.B[:0], s...) 101 | } 102 | 103 | // String returns string representation of ByteBuffer.B. 104 | func (b *ByteBuffer) String() string { 105 | return string(b.B) 106 | } 107 | 108 | // Reset makes ByteBuffer.B empty. 109 | func (b *ByteBuffer) Reset() { 110 | b.B = b.B[:0] 111 | } 112 | -------------------------------------------------------------------------------- /utils/bytebuffer/bytebufferpool.go: -------------------------------------------------------------------------------- 1 | package bytebuffer 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | const ( 10 | minBitSize = 6 // 2**6=64 is a CPU cache line size 11 | steps = 20 12 | 13 | minSize = 1 << minBitSize 14 | maxSize = 1 << (minBitSize + steps - 1) //nolint 15 | 16 | calibrateCallsThreshold = 42000 17 | maxPercentile = 0.95 18 | ) 19 | 20 | // Pool represents byte buffer pool. 21 | // 22 | // Distinct pools may be used for distinct types of byte buffers. 23 | // Properly determined byte buffer types with their own pools may help reducing 24 | // memory waste. 25 | type Pool struct { 26 | calls [steps]uint64 27 | calibrating uint64 28 | 29 | defaultSize uint64 30 | maxSize uint64 31 | 32 | pool sync.Pool 33 | } 34 | 35 | var defaultPool Pool 36 | 37 | // Get returns an empty byte buffer from the pool. 38 | // 39 | // Got byte buffer may be returned to the pool via Put call. 40 | // This reduces the number of memory allocations required for byte buffer 41 | // management. 42 | func Get() *ByteBuffer { return defaultPool.Get() } 43 | 44 | // Get returns new byte buffer with zero length. 45 | // 46 | // The byte buffer may be returned to the pool via Put after the use 47 | // in order to minimize GC overhead. 48 | func (p *Pool) Get() *ByteBuffer { 49 | v := p.pool.Get() 50 | if v != nil { 51 | return v.(*ByteBuffer) 52 | } 53 | return &ByteBuffer{ 54 | B: make([]byte, 0, atomic.LoadUint64(&p.defaultSize)), 55 | } 56 | } 57 | 58 | // Put returns byte buffer to the pool. 59 | // 60 | // ByteBuffer.B mustn't be touched after returning it to the pool. 61 | // Otherwise data races will occur. 62 | func Put(b *ByteBuffer) { defaultPool.Put(b) } 63 | 64 | // Put releases byte buffer obtained via Get to the pool. 65 | // 66 | // The buffer mustn't be accessed after returning to the pool. 67 | func (p *Pool) Put(b *ByteBuffer) { 68 | idx := index(len(b.B)) 69 | 70 | if atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold { 71 | p.calibrate() 72 | } 73 | 74 | maxSize := int(atomic.LoadUint64(&p.maxSize)) 75 | if maxSize == 0 || cap(b.B) <= maxSize { 76 | b.Reset() 77 | p.pool.Put(b) 78 | } 79 | } 80 | 81 | func (p *Pool) calibrate() { 82 | if !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) { 83 | return 84 | } 85 | 86 | a := make(callSizes, 0, steps) 87 | var callsSum uint64 88 | for i := uint64(0); i < steps; i++ { 89 | calls := atomic.SwapUint64(&p.calls[i], 0) 90 | callsSum += calls 91 | a = append(a, callSize{ 92 | calls: calls, 93 | size: minSize << i, 94 | }) 95 | } 96 | sort.Sort(a) 97 | 98 | defaultSize := a[0].size 99 | maxSize := defaultSize 100 | 101 | maxSum := uint64(float64(callsSum) * maxPercentile) 102 | callsSum = 0 103 | for i := 0; i < steps; i++ { 104 | if callsSum > maxSum { 105 | break 106 | } 107 | callsSum += a[i].calls 108 | size := a[i].size 109 | if size > maxSize { 110 | maxSize = size 111 | } 112 | } 113 | 114 | atomic.StoreUint64(&p.defaultSize, defaultSize) 115 | atomic.StoreUint64(&p.maxSize, maxSize) 116 | 117 | atomic.StoreUint64(&p.calibrating, 0) 118 | } 119 | 120 | type callSize struct { 121 | calls uint64 122 | size uint64 123 | } 124 | 125 | type callSizes []callSize 126 | 127 | func (ci callSizes) Len() int { 128 | return len(ci) 129 | } 130 | 131 | func (ci callSizes) Less(i, j int) bool { 132 | return ci[i].calls > ci[j].calls 133 | } 134 | 135 | func (ci callSizes) Swap(i, j int) { 136 | ci[i], ci[j] = ci[j], ci[i] 137 | } 138 | 139 | func index(n int) int { 140 | n-- 141 | n >>= minBitSize 142 | idx := 0 143 | for n > 0 { 144 | n >>= 1 145 | idx++ 146 | } 147 | if idx >= steps { 148 | idx = steps - 1 149 | } 150 | return idx 151 | } 152 | -------------------------------------------------------------------------------- /utils/bytebuffer/bytebufferpool_test.go: -------------------------------------------------------------------------------- 1 | package bytebuffer 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestPool_Get(t *testing.T) { 11 | got := Get() 12 | assert.NotNil(t, got) 13 | got.Write([]byte("hello")) 14 | assert.Equal(t, "hello", got.String()) 15 | got.WriteString(" world") 16 | assert.Equal(t, "hello world", got.String()) 17 | got.Set([]byte("foo")) 18 | assert.Equal(t, "foo", got.String()) 19 | assert.Equal(t, 3, got.Len()) 20 | got.SetString("bar") 21 | assert.Equal(t, "bar", got.String()) 22 | got.WriteByte('!') 23 | assert.Equal(t, "bar!", got.String()) 24 | Put(got) 25 | got2 := Get() 26 | got2.ReadFrom(strings.NewReader("hello")) 27 | assert.Equal(t, "hello", got2.String()) 28 | assert.Equal(t, []byte("hello"), got2.Bytes()) 29 | Put(got2) 30 | assert.Equal(t, 0, got.Len()) 31 | assert.Equal(t, 0, got2.Len()) 32 | } 33 | -------------------------------------------------------------------------------- /utils/execx/execx.go: -------------------------------------------------------------------------------- 1 | package execx 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "os/exec" 8 | "runtime" 9 | "strings" 10 | ) 11 | 12 | func Exec(arg string, dir ...string) (string, error) { 13 | goos := runtime.GOOS 14 | var cmd *exec.Cmd 15 | switch goos { 16 | case "darwin", "linux": 17 | cmd = exec.Command("sh", "-c", arg) 18 | case "windows": 19 | cmd = exec.Command("cmd.exe", "/c", arg) 20 | default: 21 | return "", fmt.Errorf("unsupport os type: %v", goos) 22 | } 23 | if len(dir) > 0 { 24 | cmd.Dir = dir[0] 25 | } 26 | 27 | stdout := new(bytes.Buffer) 28 | stderr := new(bytes.Buffer) 29 | 30 | cmd.Stdout = stdout 31 | cmd.Stderr = stderr 32 | err := cmd.Run() 33 | if err != nil { 34 | if stderr.Len() > 0 { 35 | return "", errors.New(stderr.String()) 36 | } 37 | return "", err 38 | } 39 | 40 | return strings.TrimSuffix(stdout.String(), "\n"), nil 41 | } 42 | -------------------------------------------------------------------------------- /utils/execx/execx_test.go: -------------------------------------------------------------------------------- 1 | package execx 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "testing" 7 | ) 8 | 9 | func TestExec(t *testing.T) { 10 | type args struct { 11 | arg string 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | want string 17 | wantErr bool 18 | }{ 19 | { 20 | name: "go version", 21 | args: args{ 22 | arg: "go version", 23 | }, 24 | want: fmt.Sprintf("go version %s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH), 25 | }, 26 | { 27 | name: "error command", 28 | args: args{ 29 | arg: "go ver", 30 | }, 31 | wantErr: true, 32 | }, 33 | { 34 | name: "error command", 35 | args: args{ 36 | arg: "ggggg", 37 | }, 38 | wantErr: true, 39 | }, 40 | } 41 | for _, tt := range tests { 42 | t.Run(tt.name, func(t *testing.T) { 43 | got, err := Exec(tt.args.arg) 44 | fmt.Print("get", got) 45 | if (err != nil) != tt.wantErr { 46 | t.Errorf("Exec() error = %v, wantErr %v", err, tt.wantErr) 47 | return 48 | } 49 | if got != tt.want { 50 | t.Errorf("Exec() = %v, want %v", got, tt.want) 51 | } 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /utils/filex/equal.go: -------------------------------------------------------------------------------- 1 | package filex 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | func IsFileEqual(a, b string) bool { 10 | f1, err := os.ReadFile(a) 11 | if err != nil { 12 | return false 13 | } 14 | 15 | f2, err := os.ReadFile(b) 16 | if err != nil { 17 | return false 18 | } 19 | 20 | normF1 := strings.ReplaceAll(string(f1), "\r\n", "\n") 21 | normF2 := strings.ReplaceAll(string(f2), "\r\n", "\n") 22 | 23 | return bytes.Equal([]byte(normF1), []byte(normF2)) 24 | } 25 | -------------------------------------------------------------------------------- /utils/filex/equal_test.go: -------------------------------------------------------------------------------- 1 | package filex 2 | 3 | import "testing" 4 | 5 | func TestIsFileEqual(t *testing.T) { 6 | type args struct { 7 | a string 8 | b string 9 | } 10 | tests := []struct { 11 | name string 12 | args args 13 | want bool 14 | }{ 15 | { 16 | name: "euqal file", 17 | args: args{ 18 | a: "testdata/a_go.mod", 19 | b: "testdata/b_go.mod", 20 | }, 21 | want: true, 22 | }, 23 | { 24 | name: "not euqal file", 25 | args: args{ 26 | a: "testdata/a_go.mod", 27 | b: "testdata/c_go.mod", 28 | }, 29 | want: false, 30 | }, 31 | { 32 | name: "not euqal file", 33 | args: args{ 34 | a: "testdata/e_go.mod", 35 | b: "testdata/c_go.mod", 36 | }, 37 | want: false, 38 | }, 39 | { 40 | name: "not euqal file", 41 | args: args{ 42 | a: "testdata/a_go.mod", 43 | b: "testdata/f_go.mod", 44 | }, 45 | want: false, 46 | }, 47 | } 48 | for _, tt := range tests { 49 | t.Run(tt.name, func(t *testing.T) { 50 | if got := IsFileEqual(tt.args.a, tt.args.b); got != tt.want { 51 | t.Errorf("IsFileEqual() = %v, want %v", got, tt.want) 52 | } 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /utils/filex/testdata/a_go.mod: -------------------------------------------------------------------------------- 1 | module github.com/metagogs/gogs/test 2 | 3 | go 1.21 -------------------------------------------------------------------------------- /utils/filex/testdata/b_go.mod: -------------------------------------------------------------------------------- 1 | module github.com/metagogs/gogs/test 2 | 3 | go 1.21 -------------------------------------------------------------------------------- /utils/filex/testdata/c_go.mod: -------------------------------------------------------------------------------- 1 | module github.com/metagogs/gogs/test 2 | 3 | go 1.18 -------------------------------------------------------------------------------- /utils/gomod/gomod.go: -------------------------------------------------------------------------------- 1 | package gomod 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/metagogs/gogs/utils/execx" 7 | ) 8 | 9 | type GoModule struct { 10 | Path string 11 | Main bool 12 | Dir string 13 | GoMod string 14 | GoVersion string 15 | } 16 | 17 | func (g *GoModule) IsInGoMod() bool { 18 | if len(g.Path) == 0 { 19 | return false 20 | } 21 | if g.Path == "command-line-arguments" { 22 | return false 23 | } 24 | if len(g.GoMod) == 0 { 25 | return false 26 | } 27 | 28 | return true 29 | } 30 | 31 | func GetMod() (*GoModule, error) { 32 | data, err := execx.Exec("go list -m -json") 33 | if err != nil { 34 | return nil, err 35 | } 36 | m := &GoModule{} 37 | if err := json.Unmarshal([]byte(data), m); err != nil { 38 | return nil, err 39 | } 40 | 41 | return m, nil 42 | } 43 | -------------------------------------------------------------------------------- /utils/name/name.go: -------------------------------------------------------------------------------- 1 | package protoparse 2 | 3 | func CamelCase(s string) string { 4 | if s == "" { 5 | return "" 6 | } 7 | t := make([]byte, 0, 32) 8 | i := 0 9 | if s[0] == '_' { 10 | // Need a capital letter; drop the '_'. 11 | t = append(t, 'X') 12 | i++ 13 | } 14 | // Invariant: if the next letter is lower case, it must be converted 15 | // to upper case. 16 | // That is, we process a word at a time, where words are marked by _ or 17 | // upper case letter. Digits are treated as words. 18 | for ; i < len(s); i++ { 19 | c := s[i] 20 | if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) { 21 | continue // Skip the underscore in s. 22 | } 23 | if isASCIIDigit(c) { 24 | t = append(t, c) 25 | continue 26 | } 27 | // Assume we have a letter now - if not, it's a bogus identifier. 28 | // The next word is a sequence of characters that must start upper case. 29 | if isASCIILower(c) { 30 | c ^= ' ' // Make it a capital letter. 31 | } 32 | t = append(t, c) // Guaranteed not lower case. 33 | // Accept lower case sequence that follows. 34 | for i+1 < len(s) && isASCIILower(s[i+1]) { 35 | i++ 36 | t = append(t, s[i]) 37 | } 38 | } 39 | return string(t) 40 | } 41 | 42 | // Is c an ASCII lower-case letter? 43 | func isASCIILower(c byte) bool { 44 | return 'a' <= c && c <= 'z' 45 | } 46 | 47 | // Is c an ASCII digit? 48 | func isASCIIDigit(c byte) bool { 49 | return '0' <= c && c <= '9' 50 | } 51 | -------------------------------------------------------------------------------- /utils/name/name_test.go: -------------------------------------------------------------------------------- 1 | package protoparse 2 | 3 | import "testing" 4 | 5 | func TestCamelCase(t *testing.T) { 6 | type args struct { 7 | s string 8 | } 9 | tests := []struct { 10 | name string 11 | args args 12 | want string 13 | }{ 14 | { 15 | args: args{ 16 | s: "", 17 | }, 18 | want: "", 19 | }, 20 | { 21 | args: args{ 22 | s: "name", 23 | }, 24 | want: "Name", 25 | }, 26 | { 27 | args: args{ 28 | s: "test_name", 29 | }, 30 | want: "TestName", 31 | }, 32 | { 33 | args: args{ 34 | s: "NAME", 35 | }, 36 | want: "NAME", 37 | }, 38 | { 39 | args: args{ 40 | s: "gogs_is_testing", 41 | }, 42 | want: "GogsIsTesting", 43 | }, 44 | { 45 | args: args{ 46 | s: "_name", 47 | }, 48 | want: "XName", 49 | }, 50 | } 51 | for _, tt := range tests { 52 | t.Run(tt.name, func(t *testing.T) { 53 | if got := CamelCase(tt.args.s); got != tt.want { 54 | t.Errorf("CamelCase() = %v, want %v", got, tt.want) 55 | } 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /utils/randstr/randstr.go: -------------------------------------------------------------------------------- 1 | package randstr 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | "unsafe" 7 | ) 8 | 9 | const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 10 | 11 | const ( 12 | letterIdxBits = 6 // 6 bits to represent a letter index 13 | letterIdxMask = 1<= 0; { 23 | if remain == 0 { 24 | cache, remain = src.Int63(), letterIdxMax 25 | } 26 | if idx := int(cache & letterIdxMask); idx < len(letterBytes) { 27 | b[i] = letterBytes[idx] 28 | i-- 29 | } 30 | cache >>= letterIdxBits 31 | remain-- 32 | } 33 | 34 | return *(*string)(unsafe.Pointer(&b)) 35 | } 36 | -------------------------------------------------------------------------------- /utils/randstr/randstr_test.go: -------------------------------------------------------------------------------- 1 | package randstr 2 | 3 | import "testing" 4 | 5 | func TestRandStr(t *testing.T) { 6 | type args struct { 7 | n int 8 | } 9 | tests := []struct { 10 | name string 11 | args args 12 | want int 13 | }{ 14 | { 15 | name: "rand", 16 | args: args{ 17 | n: 17, 18 | }, 19 | want: 17, 20 | }, 21 | } 22 | for _, tt := range tests { 23 | t.Run(tt.name, func(t *testing.T) { 24 | if got := RandStr(tt.args.n); len(got) != tt.want { 25 | t.Errorf("RandStr() = %v, want %v", got, tt.want) 26 | } 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /utils/slicex/slicex.go: -------------------------------------------------------------------------------- 1 | package slicex 2 | 3 | func InSlice[T comparable](target T, data []T) bool { 4 | for _, s := range data { 5 | if s == target { 6 | return true 7 | } 8 | } 9 | 10 | return false 11 | } 12 | 13 | func RemoveSliceItem[T comparable](list []T, item T) []T { 14 | for i, v := range list { 15 | if v == item { 16 | list = append(list[:i], list[i+1:]...) 17 | break 18 | } 19 | } 20 | return list 21 | } 22 | -------------------------------------------------------------------------------- /utils/slicex/slicex_test.go: -------------------------------------------------------------------------------- 1 | package slicex 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestInSlice(t *testing.T) { 9 | type args struct { 10 | target string 11 | data []string 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | want bool 17 | }{ 18 | { 19 | name: "test1", 20 | args: args{ 21 | target: "a", 22 | data: []string{"a", "b", "c"}, 23 | }, 24 | want: true, 25 | }, 26 | { 27 | name: "test2", 28 | args: args{ 29 | target: "d", 30 | data: []string{"a", "b", "c"}, 31 | }, 32 | want: false, 33 | }, 34 | { 35 | name: "test3", 36 | args: args{ 37 | target: "d", 38 | data: []string{}, 39 | }, 40 | want: false, 41 | }, 42 | } 43 | for _, tt := range tests { 44 | t.Run(tt.name, func(t *testing.T) { 45 | if got := InSlice(tt.args.target, tt.args.data); got != tt.want { 46 | t.Errorf("InSlice() = %v, want %v", got, tt.want) 47 | } 48 | }) 49 | } 50 | 51 | type argsInt struct { 52 | target int 53 | data []int 54 | } 55 | testsInt := []struct { 56 | name string 57 | args argsInt 58 | want bool 59 | }{ 60 | { 61 | name: "test1", 62 | args: argsInt{ 63 | target: 1, 64 | data: []int{1, 2, 3}, 65 | }, 66 | want: true, 67 | }, 68 | { 69 | name: "test2", 70 | args: argsInt{ 71 | target: 4, 72 | data: []int{1, 2, 3}, 73 | }, 74 | want: false, 75 | }, 76 | { 77 | name: "test3", 78 | args: argsInt{ 79 | target: 1, 80 | data: []int{}, 81 | }, 82 | want: false, 83 | }, 84 | } 85 | for _, tt := range testsInt { 86 | t.Run(tt.name, func(t *testing.T) { 87 | if got := InSlice(tt.args.target, tt.args.data); got != tt.want { 88 | t.Errorf("InSlice() = %v, want %v", got, tt.want) 89 | } 90 | }) 91 | } 92 | } 93 | 94 | func TestRemoveSliceItem(t *testing.T) { 95 | type args struct { 96 | list []string 97 | item string 98 | } 99 | tests := []struct { 100 | name string 101 | args args 102 | want []string 103 | }{ 104 | { 105 | name: "test1", 106 | args: args{ 107 | list: []string{"a", "b", "c"}, 108 | item: "b", 109 | }, 110 | want: []string{"a", "c"}, 111 | }, 112 | { 113 | name: "test2", 114 | args: args{ 115 | list: []string{"a", "b", "c"}, 116 | item: "d", 117 | }, 118 | want: []string{"a", "b", "c"}, 119 | }, 120 | { 121 | name: "test3", 122 | args: args{ 123 | list: []string{}, 124 | item: "d", 125 | }, 126 | want: []string{}, 127 | }, 128 | } 129 | for _, tt := range tests { 130 | t.Run(tt.name, func(t *testing.T) { 131 | if got := RemoveSliceItem(tt.args.list, tt.args.item); !reflect.DeepEqual(got, tt.want) { 132 | t.Errorf("RemoveSliceItem() = %v, want %v", got, tt.want) 133 | } 134 | }) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /utils/snow/ip.go: -------------------------------------------------------------------------------- 1 | package snow 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | func GetLocalIP() (string, error) { 11 | addrs, err := net.InterfaceAddrs() 12 | 13 | if err != nil { 14 | return "", err 15 | } 16 | 17 | for _, address := range addrs { 18 | if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 19 | if ipnet.IP.To4() != nil { 20 | return ipnet.IP.String(), nil 21 | } 22 | } 23 | } 24 | 25 | return "", fmt.Errorf("can not get the ip") 26 | } 27 | 28 | func IP4toInt16(ip string) int64 { 29 | bits := strings.Split(ip, ".") 30 | 31 | b2, _ := strconv.Atoi(bits[2]) 32 | b3, _ := strconv.Atoi(bits[3]) 33 | 34 | var sum int64 35 | 36 | sum += int64(b2) << 8 37 | sum += int64(b3) 38 | 39 | return sum 40 | } 41 | -------------------------------------------------------------------------------- /utils/snow/ip_test.go: -------------------------------------------------------------------------------- 1 | package snow 2 | 3 | import "testing" 4 | 5 | func TestGetLocalIP(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | want string 9 | wantErr bool 10 | }{ 11 | { 12 | name: "get local ip", 13 | want: "", 14 | wantErr: false, 15 | }, 16 | } 17 | for _, tt := range tests { 18 | t.Run(tt.name, func(t *testing.T) { 19 | _, err := GetLocalIP() 20 | if (err != nil) != tt.wantErr { 21 | t.Errorf("GetLocalIP() error = %v, wantErr %v", err, tt.wantErr) 22 | return 23 | } 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /utils/snow/snow.go: -------------------------------------------------------------------------------- 1 | package snow 2 | 3 | import ( 4 | "math/rand" 5 | 6 | "github.com/bwmarrin/snowflake" 7 | ) 8 | 9 | func getNodeID() int64 { 10 | var nodeID int64 11 | ip, err := GetLocalIP() 12 | if err != nil || len(ip) == 0 { 13 | nodeID = rand.Int63n(2 << 14) //nolint 14 | } else { 15 | nodeID = IP4toInt16(ip) 16 | } 17 | 18 | return nodeID 19 | } 20 | 21 | func NewSnowNode() (*snowflake.Node, error) { 22 | nodeID := getNodeID() 23 | snowflake.NodeBits = 16 24 | snowflake.StepBits = 6 25 | sf, err := snowflake.NewNode(nodeID) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return sf, nil 31 | } 32 | -------------------------------------------------------------------------------- /utils/snow/snow_test.go: -------------------------------------------------------------------------------- 1 | package snow 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "testing" 7 | 8 | "github.com/bwmarrin/snowflake" 9 | ) 10 | 11 | func Test_getNodeID(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | wantErr bool 15 | nodeID int64 16 | }{ 17 | { 18 | name: "getNodeID", 19 | wantErr: false, 20 | nodeID: getNodeID(), 21 | }, 22 | { 23 | name: "rand rand id", 24 | wantErr: false, 25 | nodeID: rand.Int63n(2 << 14), 26 | }, 27 | { 28 | name: "max rand id", 29 | wantErr: true, 30 | nodeID: 2 << 15, 31 | }, 32 | { 33 | name: "get ip node", 34 | wantErr: false, 35 | nodeID: int64(0)<<8 + int64(1), 36 | }, 37 | { 38 | name: "get ip node", 39 | wantErr: false, 40 | nodeID: int64(255)<<8 + int64(255), 41 | }, 42 | { 43 | name: "get ip node", 44 | wantErr: false, 45 | nodeID: int64(128)<<8 + int64(128), 46 | }, 47 | } 48 | for _, tt := range tests { 49 | t.Run(tt.name, func(t *testing.T) { 50 | snowflake.NodeBits = 16 51 | snowflake.StepBits = 6 52 | sf, err := snowflake.NewNode(tt.nodeID) 53 | 54 | if (err != nil) != tt.wantErr { 55 | t.Errorf("GetLocalIP() error = %v, wantErr %v", err, tt.wantErr) 56 | return 57 | } 58 | if !tt.wantErr { 59 | fmt.Println(sf.Generate().Int64()) 60 | } 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /utils/stringx/stringx.go: -------------------------------------------------------------------------------- 1 | package stringx 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | func StringToBytes(s string) []byte { 8 | return *(*[]byte)(unsafe.Pointer(&s)) 9 | } 10 | 11 | // toString performs unholy acts to avoid allocations 12 | func BytesToString(b []byte) string { 13 | return *(*string)(unsafe.Pointer(&b)) 14 | } 15 | -------------------------------------------------------------------------------- /utils/stringx/stringx_test.go: -------------------------------------------------------------------------------- 1 | package stringx 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkStringToBytesPointer(b *testing.B) { 8 | data := "sfsdfjsdkfjsdklfjskldajflkdsajflkjasdklfjskldjflkdsajflkajsdflkjasdklfj" 9 | b.ResetTimer() 10 | for i := 0; i < b.N; i++ { 11 | _ = StringToBytes(data) 12 | } 13 | } 14 | 15 | func BenchmarkStringToBytes(b *testing.B) { 16 | data := "sfsdfjsdkfjsdklfjskldajflkdsajflkjasdklfjskldjflkdsajflkajsdflkjasdklfj" 17 | b.ResetTimer() 18 | for i := 0; i < b.N; i++ { 19 | _ = []byte(data) 20 | } 21 | } 22 | 23 | func BenchmarkBytesToStringPointer(b *testing.B) { 24 | data := []byte("sfsdfjsdkfjsdklfjskldajflkdsajflkjasdklfjskldjflkdsajflkajsdflkjasdklfj") 25 | b.ResetTimer() 26 | for i := 0; i < b.N; i++ { 27 | _ = BytesToString(data) 28 | } 29 | } 30 | 31 | func BenchmarkBytesToString(b *testing.B) { 32 | data := []byte("sfsdfjsdkfjsdklfjskldajflkdsajflkjasdklfjskldjflkdsajflkajsdflkjasdklfj") 33 | b.ResetTimer() 34 | for i := 0; i < b.N; i++ { 35 | _ = string(data) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /utils/templatex/templatex.go: -------------------------------------------------------------------------------- 1 | package templatex 2 | 3 | import ( 4 | "bytes" 5 | goformat "go/format" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | "text/template" 10 | 11 | nameUtil "github.com/metagogs/gogs/utils/name" 12 | ) 13 | 14 | const regularPerm = 0o666 15 | 16 | // DefaultTemplate is a tool to provides the text/template operations 17 | type DefaultTemplate struct { 18 | name string 19 | text string 20 | goFmt bool 21 | funcMap template.FuncMap 22 | savePath string 23 | } 24 | 25 | // With returns a instance of DefaultTemplate 26 | func With(name string) *DefaultTemplate { 27 | d := &DefaultTemplate{ 28 | name: name, 29 | } 30 | d.funcMap = template.FuncMap{ 31 | "ToUpper": strings.ToUpper, 32 | "ToLower": strings.ToLower, 33 | "CamelCase": nameUtil.CamelCase, 34 | } 35 | return d 36 | } 37 | 38 | // Parse accepts a source template and returns DefaultTemplate 39 | func (t *DefaultTemplate) Parse(text string) *DefaultTemplate { 40 | t.text = text 41 | return t 42 | } 43 | 44 | func (t *DefaultTemplate) Funcs(f template.FuncMap) *DefaultTemplate { 45 | t.funcMap = f 46 | return t 47 | } 48 | 49 | // GoFmt sets the value to goFmt and marks the generated codes will be formatted or not 50 | func (t *DefaultTemplate) GoFmt(format bool) *DefaultTemplate { 51 | t.goFmt = format 52 | return t 53 | } 54 | 55 | // SaveTo writes the codes to the target path 56 | func (t *DefaultTemplate) SaveTo(data interface{}, path string, forceUpdate bool) error { 57 | if FileExists(path) && !forceUpdate { 58 | return nil 59 | } 60 | 61 | output, err := t.Execute(data) 62 | if err != nil { 63 | return err 64 | } 65 | _ = os.MkdirAll(filepath.Dir(path), os.ModePerm) 66 | 67 | return os.WriteFile(path, output.Bytes(), regularPerm) 68 | } 69 | 70 | // Execute returns the codes after the template executed 71 | func (t *DefaultTemplate) Execute(data interface{}) (*bytes.Buffer, error) { 72 | tem, err := template.New(t.name).Funcs(t.funcMap).Parse(t.text) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | buf := new(bytes.Buffer) 78 | if err = tem.Execute(buf, data); err != nil { 79 | return nil, err 80 | } 81 | 82 | if !t.goFmt { 83 | return buf, nil 84 | } 85 | 86 | formatOutput, err := goformat.Source(buf.Bytes()) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | buf.Reset() 92 | buf.Write(formatOutput) 93 | return buf, nil 94 | } 95 | 96 | func FileExists(file string) bool { 97 | _, err := os.Stat(file) 98 | return err == nil 99 | } 100 | -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | v0.3.1 -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package gogs 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | //go:embed version 8 | var Version string 9 | -------------------------------------------------------------------------------- /webserver/webserver.go: -------------------------------------------------------------------------------- 1 | package webserver 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/pprof" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/metagogs/gogs/config" 11 | ) 12 | 13 | // WebServer 14 | // Provide the web http server with different port 15 | // Use the gin framework 16 | type WebServer struct { 17 | config *config.Config 18 | httpServe map[int]*gin.Engine // http服务 19 | } 20 | 21 | func NewWebServer(config *config.Config) *WebServer { 22 | return &WebServer{ 23 | httpServe: make(map[int]*gin.Engine), 24 | config: config, 25 | } 26 | } 27 | 28 | // RegisterWebHandler register web handler, use the custom port and callback the gin engine for user 29 | func (app *WebServer) RegisterWebHandler(port int, f func(gin *gin.Engine)) { 30 | if _, exist := app.httpServe[port]; exist { 31 | panic("port is used") 32 | } 33 | app.httpServe[port] = app.createServer() 34 | f(app.httpServe[port]) 35 | } 36 | 37 | // createServer create the gin engine 38 | func (app *WebServer) createServer() *gin.Engine { 39 | return gin.New() 40 | } 41 | 42 | func (app *WebServer) Start() { 43 | for port, g := range app.httpServe { 44 | go func(e *gin.Engine, p int) { 45 | _ = e.Run(fmt.Sprintf("0.0.0.0:%d", p)) 46 | }(g, port) 47 | } 48 | if app.config.Debug { 49 | // in the debug mode show the pprof handle 50 | app.debugServer() 51 | } 52 | } 53 | 54 | // debugServer the pprof handle 55 | func (app *WebServer) debugServer() { 56 | pp := http.NewServeMux() 57 | pp.HandleFunc("/debug/pprof/", pprof.Index) 58 | pp.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 59 | pp.HandleFunc("/debug/pprof/profile", pprof.Profile) 60 | pp.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 61 | pp.HandleFunc("/debug/pprof/trace", pprof.Trace) 62 | server := &http.Server{ 63 | Addr: fmt.Sprintf(":%d", app.config.GopprofAddr), 64 | Handler: pp, 65 | ReadHeaderTimeout: 5 * time.Second, 66 | } 67 | go func() { 68 | _ = server.ListenAndServe() 69 | }() 70 | } 71 | --------------------------------------------------------------------------------