Skip to content

Commit 5d74bb0

Browse files
committed
Add match signal function.
1 parent e5f52b5 commit 5d74bb0

21 files changed

+422
-80
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr
66
## [Unreleased]
77
### Added
88
- Add final notification sent to sockets closed via single socket option.
9+
- Add match signal function to server framework.
10+
11+
### Changed
12+
- Match handlers are now required to implement a signal handler function.
913

1014
## [3.7.0] - 2021-09-28
1115
### Added

data/modules/match.lua

+54-8
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ Dispatcher exposes useful functions to the match. Format:
7777
}
7878
7979
Tick is the current match tick number, starts at 0 and increments after every match_loop call. Does not increment with
80-
calls to match_join_attempt, match_join, or match_leave.
80+
calls to match_join_attempt, match_join, match_leave, match_terminate, or match_signal.
8181
8282
State is the current in-memory match state, may be any Lua term except nil.
8383
@@ -137,7 +137,7 @@ Dispatcher exposes useful functions to the match. Format:
137137
}
138138
139139
Tick is the current match tick number, starts at 0 and increments after every match_loop call. Does not increment with
140-
calls to match_join_attempt, match_join, or match_leave.
140+
calls to match_join_attempt, match_join, match_leave, match_terminate, or match_signal.
141141
142142
State is the current in-memory match state, may be any Lua term except nil.
143143
@@ -189,7 +189,7 @@ Dispatcher exposes useful functions to the match. Format:
189189
}
190190
191191
Tick is the current match tick number, starts at 0 and increments after every match_loop call. Does not increment with
192-
calls to match_join_attempt, match_join, or match_leave.
192+
calls to match_join_attempt, match_join, match_leave, match_terminate, or match_signal.
193193
194194
State is the current in-memory match state, may be any Lua term except nil.
195195
@@ -241,7 +241,7 @@ Dispatcher exposes useful functions to the match. Format:
241241
}
242242
243243
Tick is the current match tick number, starts at 0 and increments after every match_loop call. Does not increment with
244-
calls to match_join_attempt, match_join, or match_leave.
244+
calls to match_join_attempt, match_join, match_leave, match_terminate, or match_signal.
245245
246246
State is the current in-memory match state, may be any Lua term except nil.
247247
@@ -268,9 +268,9 @@ local function match_loop(context, dispatcher, tick, state, messages)
268268
print("match " .. context.match_id .. " tick " .. tick)
269269
print("match " .. context.match_id .. " messages:\n" .. du.print_r(messages))
270270
end
271-
if tick < 10 then
271+
-- if tick < 10 then
272272
return state
273-
end
273+
-- end
274274
end
275275

276276
--[[
@@ -300,7 +300,7 @@ Dispatcher exposes useful functions to the match. Format:
300300
}
301301
302302
Tick is the current match tick number, starts at 0 and increments after every match_loop call. Does not increment with
303-
calls to match_join_attempt, match_join, or match_leave.
303+
calls to match_join_attempt, match_join, match_leave, match_terminate, or match_signal.
304304
305305
State is the current in-memory match state, may be any Lua term except nil.
306306
@@ -317,12 +317,58 @@ local function match_terminate(context, dispatcher, tick, state, grace_seconds)
317317
return state
318318
end
319319

320+
--[[
321+
Called when the match handler receives a runtime signal.
322+
323+
Context represents information about the match and server, for information purposes. Format:
324+
{
325+
env = {}, -- key-value data set in the runtime.env server configuration.
326+
executionMode = "Match",
327+
match_id = "client-friendly match ID, can be shared with clients and used in match join operations",
328+
match_node = "name of the Nakama node hosting this match",
329+
match_label = "the label string returned from match_init",
330+
match_tick_rate = 1 -- the tick rate returned by match_init
331+
}
332+
333+
Dispatcher exposes useful functions to the match. Format:
334+
{
335+
broadcast_message = function(op_code, data, presences, sender),
336+
-- numeric message op code
337+
-- a data payload string, or nil
338+
-- list of presences (a subset of match participants) to use as message targets, or nil to send to the whole match
339+
-- a presence to tag on the message as the 'sender', or nil
340+
match_kick = function(presences)
341+
-- a list of presences to remove from the match
342+
match_label_update = function(label)
343+
-- a new label to set for the match
344+
}
345+
346+
Tick is the current match tick number, starts at 0 and increments after every match_loop call. Does not increment with
347+
calls to match_join_attempt, match_join, match_leave, match_terminate, or match_signal.
348+
349+
State is the current in-memory match state, may be any Lua term except nil.
350+
351+
Data is arbitrary input supplied by the runtime caller of the signal.
352+
353+
Expected return these values (all required) in order:
354+
1. An (optionally) updated state. May be any non-nil Lua term, or nil to end the match.
355+
1. Arbitrary data to return to the runtime caller of the signal. May be a string, or nil.
356+
--]]
357+
local function match_signal(context, dispatcher, tick, state, data)
358+
if state.debug then
359+
print("match " .. context.match_id .. " tick " .. tick)
360+
print("match " .. context.match_id .. " data " .. data)
361+
end
362+
return state, "signal received: " .. data
363+
end
364+
320365
-- Match modules must return a table with these functions defined. All functions are required.
321366
return {
322367
match_init = match_init,
323368
match_join_attempt = match_join_attempt,
324369
match_join = match_join,
325370
match_leave = match_leave,
326371
match_loop = match_loop,
327-
match_terminate = match_terminate
372+
match_terminate = match_terminate,
373+
match_signal = match_signal
328374
}

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ require (
1313
github.com/gorilla/mux v1.8.0
1414
github.com/gorilla/websocket v1.4.2
1515
github.com/grpc-ecosystem/grpc-gateway/v2 v2.3.0
16-
github.com/heroiclabs/nakama-common v1.18.0
16+
github.com/heroiclabs/nakama-common v0.0.0-20211005124542-143e8d8ce05e
1717
github.com/jackc/pgconn v1.8.1
1818
github.com/jackc/pgerrcode v0.0.0-20201024163028-a0d42d470451
1919
github.com/jackc/pgtype v1.7.0

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
310310
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
311311
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
312312
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
313+
github.com/heroiclabs/nakama-common v0.0.0-20211005124542-143e8d8ce05e h1:U056PgGxvN0QSgdOR2JrUIkDLydUxh+EOiw5ilLdOg4=
314+
github.com/heroiclabs/nakama-common v0.0.0-20211005124542-143e8d8ce05e/go.mod h1:jzIGV5bI45ALRQFzHPkJn4Z0tV+xhtho1+pZhOXVAsk=
313315
github.com/heroiclabs/nakama-common v1.18.0 h1:afrtYzS9M73j5d02SX4h9ylFpgO0qKxLewgkc16l1jA=
314316
github.com/heroiclabs/nakama-common v1.18.0/go.mod h1:jzIGV5bI45ALRQFzHPkJn4Z0tV+xhtho1+pZhOXVAsk=
315317
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=

main.go

+4-55
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,20 @@ package main
1616

1717
import (
1818
"context"
19-
"database/sql"
2019
"flag"
2120
"fmt"
22-
"google.golang.org/protobuf/encoding/protojson"
21+
"io/ioutil"
2322
"math/rand"
2423
"net/http"
2524
"net/url"
2625
"os"
2726
"os/signal"
27+
"path/filepath"
2828
"runtime"
2929
"strings"
3030
"syscall"
3131
"time"
3232

33-
"io/ioutil"
34-
"path/filepath"
35-
3633
"github.com/gofrs/uuid"
3734
"github.com/heroiclabs/nakama/v3/ga"
3835
"github.com/heroiclabs/nakama/v3/migrate"
@@ -41,6 +38,7 @@ import (
4138
_ "github.com/jackc/pgx/v4/stdlib"
4239
"go.uber.org/zap"
4340
"go.uber.org/zap/zapcore"
41+
"google.golang.org/protobuf/encoding/protojson"
4442
)
4543

4644
const cookieFilename = ".cookie"
@@ -116,7 +114,7 @@ func main() {
116114
}
117115
startupLogger.Info("Database connections", zap.Strings("dsns", redactedAddresses))
118116

119-
db, dbVersion := dbConnect(startupLogger, config)
117+
db, dbVersion := server.DbConnect(startupLogger, config)
120118
startupLogger.Info("Database information", zap.String("version", dbVersion))
121119

122120
// Global server context.
@@ -234,55 +232,6 @@ func main() {
234232
os.Exit(0)
235233
}
236234

237-
func dbConnect(multiLogger *zap.Logger, config server.Config) (*sql.DB, string) {
238-
rawURL := config.GetDatabase().Addresses[0]
239-
if !(strings.HasPrefix(rawURL, "postgresql://") || strings.HasPrefix(rawURL, "postgres://")) {
240-
rawURL = fmt.Sprintf("postgres://%s", rawURL)
241-
}
242-
parsedURL, err := url.Parse(rawURL)
243-
if err != nil {
244-
multiLogger.Fatal("Bad database connection URL", zap.Error(err))
245-
}
246-
query := parsedURL.Query()
247-
if len(query.Get("sslmode")) == 0 {
248-
query.Set("sslmode", "prefer")
249-
parsedURL.RawQuery = query.Encode()
250-
}
251-
252-
if len(parsedURL.User.Username()) < 1 {
253-
parsedURL.User = url.User("root")
254-
}
255-
if len(parsedURL.Path) < 1 {
256-
parsedURL.Path = "/nakama"
257-
}
258-
259-
multiLogger.Debug("Complete database connection URL", zap.String("raw_url", parsedURL.String()))
260-
db, err := sql.Open("pgx", parsedURL.String())
261-
if err != nil {
262-
multiLogger.Fatal("Error connecting to database", zap.Error(err))
263-
}
264-
// Limit the time allowed to ping database and get version to 15 seconds total.
265-
ctx, ctxCancelFn := context.WithTimeout(context.Background(), 15*time.Second)
266-
defer ctxCancelFn()
267-
if err = db.PingContext(ctx); err != nil {
268-
if strings.HasSuffix(err.Error(), "does not exist (SQLSTATE 3D000)") {
269-
multiLogger.Fatal("Database schema not found, run `nakama migrate up`", zap.Error(err))
270-
}
271-
multiLogger.Fatal("Error pinging database", zap.Error(err))
272-
}
273-
274-
db.SetConnMaxLifetime(time.Millisecond * time.Duration(config.GetDatabase().ConnMaxLifetimeMs))
275-
db.SetMaxOpenConns(config.GetDatabase().MaxOpenConns)
276-
db.SetMaxIdleConns(config.GetDatabase().MaxIdleConns)
277-
278-
var dbVersion string
279-
if err = db.QueryRowContext(ctx, "SELECT version()").Scan(&dbVersion); err != nil {
280-
multiLogger.Fatal("Error querying database version", zap.Error(err))
281-
}
282-
283-
return db, dbVersion
284-
}
285-
286235
// Help improve Nakama by sending anonymous usage statistics.
287236
//
288237
// You can disable the telemetry completely before server start by setting the

sample_go_module/sample.go

+8
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,14 @@ func (m *Match) MatchTerminate(ctx context.Context, logger runtime.Logger, db *s
158158
return state
159159
}
160160

161+
func (m *Match) MatchSignal(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, data string) (interface{}, string) {
162+
if state.(*MatchState).debug {
163+
logger.Info("match signal match_id %v tick %v", ctx.Value(runtime.RUNTIME_CTX_MATCH_ID), tick)
164+
logger.Info("match signal match_id %v data %v", ctx.Value(runtime.RUNTIME_CTX_MATCH_ID), data)
165+
}
166+
return state, "signal received: " + data
167+
}
168+
161169
func eventSessionStart(ctx context.Context, logger runtime.Logger, evt *api.Event) {
162170
logger.Info("session start %v %v", ctx, evt)
163171
}

server/api_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ func (d *DummySession) SendBytes(payload []byte, reliable bool) error {
113113
return nil
114114
}
115115

116-
func (d *DummySession) Close(msg string, reason runtime.PresenceReason) {}
116+
func (d *DummySession) Close(msg string, reason runtime.PresenceReason, envelopes ...*rtapi.Envelope) {
117+
}
117118

118119
type loggerEnabler struct{}
119120

server/config.go

+5
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,9 @@ func CheckConfig(logger *zap.Logger, config Config) map[string]string {
236236
if config.GetMatch().CallQueueSize < 1 {
237237
logger.Fatal("Match call queue size must be >= 1", zap.Int("match.call_queue_size", config.GetMatch().CallQueueSize))
238238
}
239+
if config.GetMatch().SignalQueueSize < 1 {
240+
logger.Fatal("Match signal queue size must be >= 1", zap.Int("match.signal_queue_size", config.GetMatch().SignalQueueSize))
241+
}
239242
if config.GetMatch().JoinAttemptQueueSize < 1 {
240243
logger.Fatal("Match join attempt queue size must be >= 1", zap.Int("match.join_attempt_queue_size", config.GetMatch().JoinAttemptQueueSize))
241244
}
@@ -839,6 +842,7 @@ func NewRuntimeConfig() *RuntimeConfig {
839842
type MatchConfig struct {
840843
InputQueueSize int `yaml:"input_queue_size" json:"input_queue_size" usage:"Size of the authoritative match buffer that stores client messages until they can be processed by the next tick. Default 128."`
841844
CallQueueSize int `yaml:"call_queue_size" json:"call_queue_size" usage:"Size of the authoritative match buffer that sequences calls to match handler callbacks to ensure no overlaps. Default 128."`
845+
SignalQueueSize int `yaml:"signal_queue_size" json:"signal_queue_size" usage:"Size of the authoritative match buffer that sequences signal operations to match handler callbacks to ensure no overlaps. Default 10."`
842846
JoinAttemptQueueSize int `yaml:"join_attempt_queue_size" json:"join_attempt_queue_size" usage:"Size of the authoritative match buffer that limits the number of in-progress join attempts. Default 128."`
843847
DeferredQueueSize int `yaml:"deferred_queue_size" json:"deferred_queue_size" usage:"Size of the authoritative match buffer that holds deferred message broadcasts until the end of each loop execution. Default 128."`
844848
JoinMarkerDeadlineMs int `yaml:"join_marker_deadline_ms" json:"join_marker_deadline_ms" usage:"Deadline in milliseconds that client authoritative match joins will wait for match handlers to acknowledge joins. Default 15000."`
@@ -851,6 +855,7 @@ func NewMatchConfig() *MatchConfig {
851855
return &MatchConfig{
852856
InputQueueSize: 128,
853857
CallQueueSize: 128,
858+
SignalQueueSize: 10,
854859
JoinAttemptQueueSize: 128,
855860
DeferredQueueSize: 128,
856861
JoinMarkerDeadlineMs: 15000,

server/db.go

+54
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,65 @@ import (
1818
"context"
1919
"database/sql"
2020
"errors"
21+
"fmt"
22+
"net/url"
23+
"strings"
24+
"time"
2125

2226
"github.com/jackc/pgconn"
2327
"github.com/jackc/pgerrcode"
28+
"go.uber.org/zap"
2429
)
2530

31+
func DbConnect(multiLogger *zap.Logger, config Config) (*sql.DB, string) {
32+
rawURL := config.GetDatabase().Addresses[0]
33+
if !(strings.HasPrefix(rawURL, "postgresql://") || strings.HasPrefix(rawURL, "postgres://")) {
34+
rawURL = fmt.Sprintf("postgres://%s", rawURL)
35+
}
36+
parsedURL, err := url.Parse(rawURL)
37+
if err != nil {
38+
multiLogger.Fatal("Bad database connection URL", zap.Error(err))
39+
}
40+
query := parsedURL.Query()
41+
if len(query.Get("sslmode")) == 0 {
42+
query.Set("sslmode", "prefer")
43+
parsedURL.RawQuery = query.Encode()
44+
}
45+
46+
if len(parsedURL.User.Username()) < 1 {
47+
parsedURL.User = url.User("root")
48+
}
49+
if len(parsedURL.Path) < 1 {
50+
parsedURL.Path = "/nakama"
51+
}
52+
53+
multiLogger.Debug("Complete database connection URL", zap.String("raw_url", parsedURL.String()))
54+
db, err := sql.Open("pgx", parsedURL.String())
55+
if err != nil {
56+
multiLogger.Fatal("Error connecting to database", zap.Error(err))
57+
}
58+
// Limit the time allowed to ping database and get version to 15 seconds total.
59+
ctx, ctxCancelFn := context.WithTimeout(context.Background(), 15*time.Second)
60+
defer ctxCancelFn()
61+
if err = db.PingContext(ctx); err != nil {
62+
if strings.HasSuffix(err.Error(), "does not exist (SQLSTATE 3D000)") {
63+
multiLogger.Fatal("Database schema not found, run `nakama migrate up`", zap.Error(err))
64+
}
65+
multiLogger.Fatal("Error pinging database", zap.Error(err))
66+
}
67+
68+
db.SetConnMaxLifetime(time.Millisecond * time.Duration(config.GetDatabase().ConnMaxLifetimeMs))
69+
db.SetMaxOpenConns(config.GetDatabase().MaxOpenConns)
70+
db.SetMaxIdleConns(config.GetDatabase().MaxIdleConns)
71+
72+
var dbVersion string
73+
if err = db.QueryRowContext(ctx, "SELECT version()").Scan(&dbVersion); err != nil {
74+
multiLogger.Fatal("Error querying database version", zap.Error(err))
75+
}
76+
77+
return db, dbVersion
78+
}
79+
2680
// Tx is used to permit clients to implement custom transaction logic.
2781
type Tx interface {
2882
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)

0 commit comments

Comments
 (0)