From 0014a81ce3d7deeee1d2d440d39e617fc833359f Mon Sep 17 00:00:00 2001 From: Settel Date: Mon, 9 Aug 2021 16:24:27 +0200 Subject: [PATCH] refined idle detection, send notification to clients --- .../src/application/expireInactivePlayers.go | 11 +++++ server/src/application/playerATime.go | 32 ++++++------ server/src/application/syncHandler.go | 4 +- server/src/engine/engine.go | 31 +++++++----- server/src/engine/struct.go | 4 +- server/src/game/game.go | 29 ++--------- server/src/game/player.go | 49 +++++++++++++++++++ server/src/game/populateSyncDataCb.go | 27 ++++++++++ server/src/game/struct.go | 16 ++++-- server/src/handler/userinfo.go | 2 + server/src/syncdata/syncdata.go | 9 ++-- 11 files changed, 152 insertions(+), 62 deletions(-) create mode 100644 server/src/game/player.go create mode 100644 server/src/game/populateSyncDataCb.go diff --git a/server/src/application/expireInactivePlayers.go b/server/src/application/expireInactivePlayers.go index 973fb02..a92fee9 100644 --- a/server/src/application/expireInactivePlayers.go +++ b/server/src/application/expireInactivePlayers.go @@ -11,6 +11,17 @@ func (app *Application) expireInactivePlayersThread() { removedIds := app.expireInactivePlayers() for _, usrId := range removedIds { + usr, err := app.GetUserById(usrId) + if err != nil { + continue + } + + gm, err2 := app.GetGameById(usr.GetGameId()) + if err2 != nil { + continue + } + + gm.EventPlayerLeaves(usr) fmt.Printf("expired %s\n", usrId) } } diff --git a/server/src/application/playerATime.go b/server/src/application/playerATime.go index be9cfc9..498a59f 100644 --- a/server/src/application/playerATime.go +++ b/server/src/application/playerATime.go @@ -1,31 +1,31 @@ package application import ( - "fmt" "sirlab.de/go/knyt/user" "time" ) -func (app *Application) updatePlayerATime(usr *user.User) { +func (app *Application) updatePlayerIsConnected(usr *user.User) { + app.updatePlayerATime(usr, time.Now().Add(30*time.Second)) +} + +func (app *Application) updatePlayerIsDisconnected(usr *user.User) { + app.updatePlayerATime(usr, time.Now()) +} + +func (app *Application) updatePlayerATime(usr *user.User, t time.Time) { usrId := usr.GetId() app.mu.Lock() prevTime := app.playerATime[usrId] - if prevTime.IsZero() { - fmt.Printf("new Player %s\n", usr.GetName()) - } - app.playerATime[usrId] = time.Now() + app.playerATime[usrId] = t app.mu.Unlock() -} -func (app *Application) getActivePlayerIds() []string { - app.mu.Lock() - defer app.mu.Unlock() - - playerIds := make([]string, 0) - for usrId, _ := range app.playerATime { - playerIds = append(playerIds, usrId) + if prevTime.IsZero() { + gm, err := app.GetGameById(usr.GetGameId()) + if err != nil { + return + } + gm.EventPlayerJoins(usr) } - - return playerIds } diff --git a/server/src/application/syncHandler.go b/server/src/application/syncHandler.go index 5c1f136..154628c 100644 --- a/server/src/application/syncHandler.go +++ b/server/src/application/syncHandler.go @@ -21,8 +21,8 @@ func (app *Application) SyncHandler(usr *user.User, w http.ResponseWriter, r *ht return } - app.updatePlayerATime(usr) - + app.updatePlayerIsConnected(usr) eng := gm.GetEngine() eng.SyncHandler(w, r) + app.updatePlayerIsDisconnected(usr) } diff --git a/server/src/engine/engine.go b/server/src/engine/engine.go index c30e9ab..337c9e6 100644 --- a/server/src/engine/engine.go +++ b/server/src/engine/engine.go @@ -3,41 +3,50 @@ package engine import ( "fmt" "github.com/imkira/go-observer" - "math/rand" "sirlab.de/go/knyt/syncdata" "time" ) -func NewEngine(id string) *Engine { +func NewEngine() *Engine { engine := Engine{ - id: id, versionRef: 0, obs: observer.NewProperty(syncdata.SyncData{}), + notify: make(chan bool, 2), } + return &engine } -func (eng *Engine) Run() { - r := rand.New(rand.NewSource(time.Now().UnixNano())) +func (eng *Engine) Run(populateSyncDataCb PopulateSyncDataCb) { for { - wait := int(1 + r.Float32()*5) - for i := 0; i < wait; i++ { - time.Sleep(1 * time.Second) + select { + case <-eng.notify: + // nop + case <-time.After(15 * time.Second): + // nop } - eng.doSomething() + eng.publish(populateSyncDataCb) } } -func (eng *Engine) doSomething() { +func (eng *Engine) Update() { + eng.notify <- true +} + +func (eng *Engine) publish(populateSyncDataCb PopulateSyncDataCb) { eng.mu.Lock() defer eng.mu.Unlock() eng.versionRef++ - fmt.Printf("game %s: %d\n", eng.id, eng.versionRef) data := syncdata.SyncData{ VersionRef: eng.versionRef, } + if populateSyncDataCb != nil { + populateSyncDataCb(&data) + } + + fmt.Printf("engine versionRef %d\n", eng.versionRef) eng.obs.Update(data) } diff --git a/server/src/engine/struct.go b/server/src/engine/struct.go index 9dcf40b..acd51d2 100644 --- a/server/src/engine/struct.go +++ b/server/src/engine/struct.go @@ -3,14 +3,16 @@ package engine import ( "github.com/imkira/go-observer" "net/http" + "sirlab.de/go/knyt/syncdata" "sync" ) type HandleFunc func(http.ResponseWriter, *http.Request) +type PopulateSyncDataCb func(*syncdata.SyncData) type Engine struct { mu sync.Mutex - id string versionRef int obs observer.Property + notify chan bool } diff --git a/server/src/game/game.go b/server/src/game/game.go index 29b510b..e4559da 100644 --- a/server/src/game/game.go +++ b/server/src/game/game.go @@ -18,12 +18,13 @@ func NewGameFromFile(id, fileName string) (*Game, error) { return nil, fmt.Errorf("%s: %v\n", fileName, err) } else { gm := Game{ - id: id, - name: gmJson.Name, - eng: engine.NewEngine(id), + id: id, + name: gmJson.Name, + eng: engine.NewEngine(), + players: make(map[string]playerInfo), } - go gm.eng.Run() + go gm.eng.Run(gm.populateSyncDataCb) return &gm, nil } @@ -49,23 +50,3 @@ func (gm *Game) GetEngine() *engine.Engine { return gm.eng } - -// func (gm *Game) GetActivePlayers() []string { -// gm.mu.Lock() -// defer gm.mu.Unlock() -// -// players := make([]string, 0) -// -// now := time.Now() -// for usrId, timestamp := range gm.playerTimestamp { -// elapsed := now.Sub(timestamp) -// -// fmt.Printf("%s: %.0f\n", usrId, elapsed.Seconds()) -// -// if elapsed.Seconds() < 30.0 { -// players = append(players, usrId) -// } -// } -// -// return players -// } diff --git a/server/src/game/player.go b/server/src/game/player.go new file mode 100644 index 0000000..48cefff --- /dev/null +++ b/server/src/game/player.go @@ -0,0 +1,49 @@ +package game + +import ( + "fmt" + "sirlab.de/go/knyt/user" +) + +func (gm *Game) EventPlayerJoins(usr *user.User) { + usrId := usr.GetId() + usrName := usr.GetName() + + gm.mu.Lock() + defer gm.mu.Unlock() + + prevPlayer := gm.players[usrId] + player := playerInfo{ + id: usrId, + name: usrName, + isPlaying: true, + isIdle: false, + } + gm.players[usrId] = player + + if prevPlayer.id != "" { + fmt.Printf("player \"%s\" re-joined\n", gm.players[usrId].name) + } else { + fmt.Printf("player \"%s\" joined\n", usrName) + } + + gm.eng.Update() +} + +func (gm *Game) EventPlayerLeaves(usr *user.User) { + usrId := usr.GetId() + + gm.mu.Lock() + defer gm.mu.Unlock() + + player := gm.players[usrId] + if player.id != usrId { + return + } + player.isIdle = true + gm.players[usrId] = player + + fmt.Printf("player \"%s\" is idle\n", usr.GetName()) + + gm.eng.Update() +} diff --git a/server/src/game/populateSyncDataCb.go b/server/src/game/populateSyncDataCb.go new file mode 100644 index 0000000..a4a2e77 --- /dev/null +++ b/server/src/game/populateSyncDataCb.go @@ -0,0 +1,27 @@ +package game + +import ( + "sirlab.de/go/knyt/syncdata" +) + +func (gm *Game) populateSyncDataCb(syncData *syncdata.SyncData) { + gm.populatePlayers(syncData) +} + +func (gm *Game) populatePlayers(syncData *syncdata.SyncData) { + gm.mu.Lock() + defer gm.mu.Unlock() + + syncData.GameInfo.GameId = gm.id + players := make([]syncdata.PlayerInfo, 0) + for _, p := range gm.players { + if p.isPlaying { + players = append(players, syncdata.PlayerInfo{ + Id: p.id, + Name: p.name, + IsIdle: p.isIdle, + }) + } + } + syncData.GameInfo.Players = players +} diff --git a/server/src/game/struct.go b/server/src/game/struct.go index 1234a11..d1b0af4 100644 --- a/server/src/game/struct.go +++ b/server/src/game/struct.go @@ -5,11 +5,19 @@ import ( "sync" ) +type playerInfo struct { + id string + name string + isPlaying bool + isIdle bool +} + type Game struct { - mu sync.Mutex - id string - name string - eng *engine.Engine + mu sync.Mutex + id string + name string + players map[string]playerInfo + eng *engine.Engine } type GameJson struct { diff --git a/server/src/handler/userinfo.go b/server/src/handler/userinfo.go index 5924ace..5ac23b2 100644 --- a/server/src/handler/userinfo.go +++ b/server/src/handler/userinfo.go @@ -8,6 +8,7 @@ import ( ) type UserInfoJson struct { + Id string `json:"id"` Name string `json:"name"` Role string `json:"role"` GameId string `json:"game"` @@ -15,6 +16,7 @@ type UserInfoJson struct { func (authMux *AuthMux) GetUserInfo(usr *user.User, w http.ResponseWriter, r *http.Request) { usrLight := UserInfoJson{ + Id: usr.GetId(), Name: usr.GetName(), Role: usr.GetRole(), GameId: usr.GetGameId(), diff --git a/server/src/syncdata/syncdata.go b/server/src/syncdata/syncdata.go index 3891e61..14d71f8 100644 --- a/server/src/syncdata/syncdata.go +++ b/server/src/syncdata/syncdata.go @@ -3,14 +3,15 @@ package syncdata type PlayerInfo struct { Id string `json:"id"` Name string `json:"name"` - Active bool `json:"active"` + IsIdle bool `json:"isIdle"` } -type Gameinfo struct { +type GameInfo struct { + GameId string `json:"id"` Players []PlayerInfo `json:"players"` } type SyncData struct { - VersionRef int `json:"version"` - Gameinfo *Gameinfo `json:"game"` + VersionRef int `json:"version"` + GameInfo GameInfo `json:"game"` }