Unverified Commit 212ff0a0 authored by Simon Esposito's avatar Simon Esposito Committed by GitHub
Browse files

Add js runtime localcache (#528)

parent 3998ac7d
Loading
Loading
Loading
Loading
+8 −6
Original line number Diff line number Diff line
@@ -505,6 +505,8 @@ func NewRuntimeProviderJS(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbM
		Indent:       jsonpbMarshaler.Indent,
	}

	localCache := NewRuntimeJavascriptLocalCache()

	runtimeProviderJS := &RuntimeProviderJS{
		config:               config,
		logger:               logger,
@@ -537,7 +539,7 @@ func NewRuntimeProviderJS(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbM
	var tournamentResetFunction RuntimeTournamentResetFunction
	var leaderboardResetFunction RuntimeLeaderboardResetFunction

	callbacks, matchCallbacks, err := evalRuntimeModules(runtimeProviderJS, modCache, matchProvider, leaderboardScheduler, func(mode RuntimeExecutionMode, id string) {
	callbacks, matchCallbacks, err := evalRuntimeModules(runtimeProviderJS, modCache, matchProvider, leaderboardScheduler, localCache, func(mode RuntimeExecutionMode, id string) {
		switch mode {
		case RuntimeExecutionModeRPC:
			rpcFunctions[id] = func(ctx context.Context, queryParams map[string][]string, userID, username string, vars map[string]string, expiry int64, sessionID, clientIP, clientPort, payload string) (string, error, codes.Code) {
@@ -1385,7 +1387,7 @@ func NewRuntimeProviderJS(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbM
				return nil, nil
			}

			return NewRuntimeJavascriptMatchCore(logger, name, db, jsonpbMarshaler, jsonpbUnmarshaler, config, socialClient, leaderboardCache, leaderboardRankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, matchProvider.CreateMatch, eventFn, id, node, stopped, mc)
			return NewRuntimeJavascriptMatchCore(logger, name, db, jsonpbMarshaler, jsonpbUnmarshaler, config, socialClient, leaderboardCache, leaderboardRankCache, localCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, matchProvider.CreateMatch, eventFn, id, node, stopped, mc)
		})

	runtimeProviderJS.newFn = func() *RuntimeJS {
@@ -1398,7 +1400,7 @@ func NewRuntimeProviderJS(logger, startupLogger *zap.Logger, db *sql.DB, jsonpbM
			logger.Fatal("Failed to initialize JavaScript runtime", zap.Error(err))
		}

		nakamaModule := NewRuntimeJavascriptNakamaModule(logger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, socialClient, leaderboardCache, leaderboardRankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, eventFn, matchProvider.CreateMatch)
		nakamaModule := NewRuntimeJavascriptNakamaModule(logger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, socialClient, leaderboardCache, leaderboardRankCache, localCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, eventFn, matchProvider.CreateMatch)
		nk := runtime.ToValue(nakamaModule.Constructor(runtime))
		nkInst, err := runtime.New(nk)
		if err != nil {
@@ -1441,7 +1443,7 @@ func CheckRuntimeProviderJavascript(logger *zap.Logger, config Config) error {
		logger: logger,
		config: config,
	}
	_, _, err = evalRuntimeModules(rp, modCache, nil, nil, func(RuntimeExecutionMode, string) {}, true)
	_, _, err = evalRuntimeModules(rp, modCache, nil, nil, nil, func(RuntimeExecutionMode, string) {}, true)
	if err != nil {
		logger.Error("Failed to load JavaScript module.", zap.Error(err))
	}
@@ -1704,7 +1706,7 @@ func (rp *RuntimeProviderJS) LeaderboardReset(ctx context.Context, leaderboard r
	return errors.New("Unexpected return type from runtime Leaderboard Reset hook, must be nil.")
}

func evalRuntimeModules(rp *RuntimeProviderJS, modCache *RuntimeJSModuleCache, matchProvider *MatchProvider, leaderboardScheduler LeaderboardScheduler, announceCallbackFn func(RuntimeExecutionMode, string), dryRun bool) (*RuntimeJavascriptCallbacks, *RuntimeJavascriptMatchHandlers, error) {
func evalRuntimeModules(rp *RuntimeProviderJS, modCache *RuntimeJSModuleCache, matchProvider *MatchProvider, leaderboardScheduler LeaderboardScheduler, localCache *RuntimeJavascriptLocalCache, announceCallbackFn func(RuntimeExecutionMode, string), dryRun bool) (*RuntimeJavascriptCallbacks, *RuntimeJavascriptMatchHandlers, error) {
	logger := rp.logger

	r := goja.New()
@@ -1723,7 +1725,7 @@ func evalRuntimeModules(rp *RuntimeProviderJS, modCache *RuntimeJSModuleCache, m
		return nil, nil, err
	}

	nakamaModule := NewRuntimeJavascriptNakamaModule(rp.logger, rp.db, rp.jsonpbMarshaler, rp.jsonpbUnmarshaler, rp.config, rp.socialClient, rp.leaderboardCache, rp.leaderboardRankCache, leaderboardScheduler, rp.sessionRegistry, rp.matchRegistry, rp.tracker, rp.streamManager, rp.router, rp.eventFn, matchProvider.CreateMatch)
	nakamaModule := NewRuntimeJavascriptNakamaModule(rp.logger, rp.db, rp.jsonpbMarshaler, rp.jsonpbUnmarshaler, rp.config, rp.socialClient, rp.leaderboardCache, rp.leaderboardRankCache, localCache, leaderboardScheduler, rp.sessionRegistry, rp.matchRegistry, rp.tracker, rp.streamManager, rp.router, rp.eventFn, matchProvider.CreateMatch)
	nk := r.ToValue(nakamaModule.Constructor(r))
	nkInst, err := r.New(nk)
	if err != nil {
+50 −0
Original line number Diff line number Diff line
// Copyright 2021 The Nakama Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package server

import (
	"github.com/dop251/goja"
	"sync"
)

type RuntimeJavascriptLocalCache struct {
	sync.RWMutex
	data map[string]goja.Value
}

func NewRuntimeJavascriptLocalCache() *RuntimeJavascriptLocalCache {
	return &RuntimeJavascriptLocalCache{
		data: make(map[string]goja.Value),
	}
}

func (lc *RuntimeJavascriptLocalCache) Get(key string) (goja.Value, bool) {
	lc.RLock()
	value, found := lc.data[key]
	lc.RUnlock()
	return value, found
}

func (lc *RuntimeJavascriptLocalCache) Put(key string, value goja.Value) {
	lc.Lock()
	lc.data[key] = value
	lc.Unlock()
}

func (lc *RuntimeJavascriptLocalCache) Delete(key string) {
	lc.Lock()
	delete(lc.data, key)
	lc.Unlock()
}
+2 −2
Original line number Diff line number Diff line
@@ -62,7 +62,7 @@ type RuntimeJavaScriptMatchCore struct {
	// ctxCancelFn context.CancelFunc
}

func NewRuntimeJavascriptMatchCore(logger *zap.Logger, module string, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, rankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter, matchCreateFn RuntimeMatchCreateFunction, eventFn RuntimeEventCustomFunction, id uuid.UUID, node string, stopped *atomic.Bool, matchHandlers *jsMatchHandlers) (RuntimeMatchCore, error) {
func NewRuntimeJavascriptMatchCore(logger *zap.Logger, module string, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, rankCache LeaderboardRankCache, localCache *RuntimeJavascriptLocalCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter, matchCreateFn RuntimeMatchCreateFunction, eventFn RuntimeEventCustomFunction, id uuid.UUID, node string, stopped *atomic.Bool, matchHandlers *jsMatchHandlers) (RuntimeMatchCore, error) {
	runtime := goja.New()

	jsLogger := NewJsLogger(logger)
@@ -72,7 +72,7 @@ func NewRuntimeJavascriptMatchCore(logger *zap.Logger, module string, db *sql.DB
		logger.Fatal("Failed to initialize JavaScript runtime", zap.Error(err))
	}

	nakamaModule := NewRuntimeJavascriptNakamaModule(logger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, socialClient, leaderboardCache, rankCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, eventFn, matchCreateFn)
	nakamaModule := NewRuntimeJavascriptNakamaModule(logger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, socialClient, leaderboardCache, rankCache, localCache, leaderboardScheduler, sessionRegistry, matchRegistry, tracker, streamManager, router, eventFn, matchCreateFn)
	nk := runtime.ToValue(nakamaModule.Constructor(runtime))
	nkInst, err := runtime.New(nk)
	if err != nil {
+60 −3
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ type runtimeJavascriptNakamaModule struct {
	socialClient         *social.Client
	leaderboardCache     LeaderboardCache
	rankCache            LeaderboardRankCache
	localCache           *RuntimeJavascriptLocalCache
	leaderboardScheduler LeaderboardScheduler
	tracker              Tracker
	sessionRegistry      SessionRegistry
@@ -77,7 +78,7 @@ type runtimeJavascriptNakamaModule struct {
	eventFn       RuntimeEventCustomFunction
}

func NewRuntimeJavascriptNakamaModule(logger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, rankCache LeaderboardRankCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter, eventFn RuntimeEventCustomFunction, matchCreateFn RuntimeMatchCreateFunction) *runtimeJavascriptNakamaModule {
func NewRuntimeJavascriptNakamaModule(logger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, leaderboardCache LeaderboardCache, rankCache LeaderboardRankCache, localCache *RuntimeJavascriptLocalCache, leaderboardScheduler LeaderboardScheduler, sessionRegistry SessionRegistry, matchRegistry MatchRegistry, tracker Tracker, streamManager StreamManager, router MessageRouter, eventFn RuntimeEventCustomFunction, matchCreateFn RuntimeMatchCreateFunction) *runtimeJavascriptNakamaModule {
	return &runtimeJavascriptNakamaModule{
		logger:               logger,
		config:               config,
@@ -92,6 +93,7 @@ func NewRuntimeJavascriptNakamaModule(logger *zap.Logger, db *sql.DB, jsonpbMars
		socialClient:         socialClient,
		leaderboardCache:     leaderboardCache,
		rankCache:            rankCache,
		localCache:           localCache,
		leaderboardScheduler: leaderboardScheduler,
		httpClient: &http.Client{
			Timeout: 5 * time.Second,
@@ -225,6 +227,9 @@ func (n *runtimeJavascriptNakamaModule) mappings(r *goja.Runtime) map[string]fun
		"groupUsersPromote":               n.groupUsersPromote(r),
		"groupUsersDemote":                n.groupUsersDemote(r),
		"fileRead":                        n.fileRead(r),
		"localcacheGet":                   n.localcacheGet(r),
		"localcachePut":                   n.localcachePut(r),
		"localcacheDelete":                n.localcacheDelete(r),
	}
}

@@ -233,7 +238,7 @@ func (n *runtimeJavascriptNakamaModule) event(r *goja.Runtime) func(goja.Functio
		eventName := getJsString(r, f.Argument(0))
		properties := getJsStringMap(r, f.Argument(1))
		ts := &timestamp.Timestamp{}
		if f.Argument(2) != goja.Undefined() {
		if f.Argument(2) != goja.Undefined() && f.Argument(2) != goja.Null() {
			ts.Seconds = getJsInt(r, f.Argument(2))
		} else {
			ts.Seconds = time.Now().Unix()
@@ -2105,7 +2110,7 @@ func (n *runtimeJavascriptNakamaModule) streamUserGet(r *goja.Runtime) func(goja
		stream := getStreamData(r, streamObj)
		meta := n.tracker.GetLocalBySessionIDStreamUserID(sessionID, stream, userID)
		if meta == nil {
			return nil
			return goja.Null()
		}

		return r.ToValue(map[string]interface{}{
@@ -5534,6 +5539,58 @@ func (n *runtimeJavascriptNakamaModule) fileRead(r *goja.Runtime) func(goja.Func
	}
}

func (n *runtimeJavascriptNakamaModule) localcacheGet(r *goja.Runtime) func(goja.FunctionCall) goja.Value {
	return func(f goja.FunctionCall) goja.Value {
		key := getJsString(r, f.Argument(0))
		if key == "" {
			panic(r.NewTypeError("expects non empty key string"))
		}

		defVal := goja.Undefined()
		if f.Argument(1) != goja.Undefined() && f.Argument(1) != goja.Null() {
			defVal = f.Argument(1)
		}

		value, found := n.localCache.Get(key)
		if found {
			return value
		}

		return defVal
	}
}

func (n *runtimeJavascriptNakamaModule) localcachePut(r *goja.Runtime) func(goja.FunctionCall) goja.Value {
	return func(f goja.FunctionCall) goja.Value {
		key := getJsString(r, f.Argument(0))
		if key == "" {
			panic(r.NewTypeError("expects non empty key string"))
		}

		value := f.Argument(1)
		if value == goja.Undefined() || value == goja.Null() {
			panic(r.NewTypeError("expects a non empty value"))
		}

		n.localCache.Put(key, value)

		return goja.Undefined()
	}
}

func (n *runtimeJavascriptNakamaModule) localcacheDelete(r *goja.Runtime) func(goja.FunctionCall) goja.Value {
	return func(f goja.FunctionCall) goja.Value {
		key := getJsString(r, f.Argument(0))
		if key == "" {
			panic(r.NewTypeError("expects non empty key string"))
		}

		n.localCache.Delete(key)

		return goja.Undefined()
	}
}

func getJsString(r *goja.Runtime, v goja.Value) string {
	s, ok := v.Export().(string)
	if !ok {