refined idle detection, send notification to clients

This commit is contained in:
Settel 2021-08-09 16:24:27 +02:00
parent 342483d62d
commit 0014a81ce3
11 changed files with 152 additions and 62 deletions

View File

@ -11,6 +11,17 @@ func (app *Application) expireInactivePlayersThread() {
removedIds := app.expireInactivePlayers() removedIds := app.expireInactivePlayers()
for _, usrId := range removedIds { 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) fmt.Printf("expired %s\n", usrId)
} }
} }

View File

@ -1,31 +1,31 @@
package application package application
import ( import (
"fmt"
"sirlab.de/go/knyt/user" "sirlab.de/go/knyt/user"
"time" "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() usrId := usr.GetId()
app.mu.Lock() app.mu.Lock()
prevTime := app.playerATime[usrId] prevTime := app.playerATime[usrId]
if prevTime.IsZero() { app.playerATime[usrId] = t
fmt.Printf("new Player %s\n", usr.GetName())
}
app.playerATime[usrId] = time.Now()
app.mu.Unlock() app.mu.Unlock()
}
func (app *Application) getActivePlayerIds() []string { if prevTime.IsZero() {
app.mu.Lock() gm, err := app.GetGameById(usr.GetGameId())
defer app.mu.Unlock() if err != nil {
return
playerIds := make([]string, 0) }
for usrId, _ := range app.playerATime { gm.EventPlayerJoins(usr)
playerIds = append(playerIds, usrId)
} }
return playerIds
} }

View File

@ -21,8 +21,8 @@ func (app *Application) SyncHandler(usr *user.User, w http.ResponseWriter, r *ht
return return
} }
app.updatePlayerATime(usr) app.updatePlayerIsConnected(usr)
eng := gm.GetEngine() eng := gm.GetEngine()
eng.SyncHandler(w, r) eng.SyncHandler(w, r)
app.updatePlayerIsDisconnected(usr)
} }

View File

@ -3,41 +3,50 @@ package engine
import ( import (
"fmt" "fmt"
"github.com/imkira/go-observer" "github.com/imkira/go-observer"
"math/rand"
"sirlab.de/go/knyt/syncdata" "sirlab.de/go/knyt/syncdata"
"time" "time"
) )
func NewEngine(id string) *Engine { func NewEngine() *Engine {
engine := Engine{ engine := Engine{
id: id,
versionRef: 0, versionRef: 0,
obs: observer.NewProperty(syncdata.SyncData{}), obs: observer.NewProperty(syncdata.SyncData{}),
notify: make(chan bool, 2),
} }
return &engine return &engine
} }
func (eng *Engine) Run() { func (eng *Engine) Run(populateSyncDataCb PopulateSyncDataCb) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for { for {
wait := int(1 + r.Float32()*5) select {
for i := 0; i < wait; i++ { case <-eng.notify:
time.Sleep(1 * time.Second) // 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() eng.mu.Lock()
defer eng.mu.Unlock() defer eng.mu.Unlock()
eng.versionRef++ eng.versionRef++
fmt.Printf("game %s: %d\n", eng.id, eng.versionRef)
data := syncdata.SyncData{ data := syncdata.SyncData{
VersionRef: eng.versionRef, VersionRef: eng.versionRef,
} }
if populateSyncDataCb != nil {
populateSyncDataCb(&data)
}
fmt.Printf("engine versionRef %d\n", eng.versionRef)
eng.obs.Update(data) eng.obs.Update(data)
} }

View File

@ -3,14 +3,16 @@ package engine
import ( import (
"github.com/imkira/go-observer" "github.com/imkira/go-observer"
"net/http" "net/http"
"sirlab.de/go/knyt/syncdata"
"sync" "sync"
) )
type HandleFunc func(http.ResponseWriter, *http.Request) type HandleFunc func(http.ResponseWriter, *http.Request)
type PopulateSyncDataCb func(*syncdata.SyncData)
type Engine struct { type Engine struct {
mu sync.Mutex mu sync.Mutex
id string
versionRef int versionRef int
obs observer.Property obs observer.Property
notify chan bool
} }

View File

@ -20,10 +20,11 @@ func NewGameFromFile(id, fileName string) (*Game, error) {
gm := Game{ gm := Game{
id: id, id: id,
name: gmJson.Name, name: gmJson.Name,
eng: engine.NewEngine(id), eng: engine.NewEngine(),
players: make(map[string]playerInfo),
} }
go gm.eng.Run() go gm.eng.Run(gm.populateSyncDataCb)
return &gm, nil return &gm, nil
} }
@ -49,23 +50,3 @@ func (gm *Game) GetEngine() *engine.Engine {
return gm.eng 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
// }

49
server/src/game/player.go Normal file
View File

@ -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()
}

View File

@ -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
}

View File

@ -5,10 +5,18 @@ import (
"sync" "sync"
) )
type playerInfo struct {
id string
name string
isPlaying bool
isIdle bool
}
type Game struct { type Game struct {
mu sync.Mutex mu sync.Mutex
id string id string
name string name string
players map[string]playerInfo
eng *engine.Engine eng *engine.Engine
} }

View File

@ -8,6 +8,7 @@ import (
) )
type UserInfoJson struct { type UserInfoJson struct {
Id string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Role string `json:"role"` Role string `json:"role"`
GameId string `json:"game"` GameId string `json:"game"`
@ -15,6 +16,7 @@ type UserInfoJson struct {
func (authMux *AuthMux) GetUserInfo(usr *user.User, w http.ResponseWriter, r *http.Request) { func (authMux *AuthMux) GetUserInfo(usr *user.User, w http.ResponseWriter, r *http.Request) {
usrLight := UserInfoJson{ usrLight := UserInfoJson{
Id: usr.GetId(),
Name: usr.GetName(), Name: usr.GetName(),
Role: usr.GetRole(), Role: usr.GetRole(),
GameId: usr.GetGameId(), GameId: usr.GetGameId(),

View File

@ -3,14 +3,15 @@ package syncdata
type PlayerInfo struct { type PlayerInfo struct {
Id string `json:"id"` Id string `json:"id"`
Name string `json:"name"` 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"` Players []PlayerInfo `json:"players"`
} }
type SyncData struct { type SyncData struct {
VersionRef int `json:"version"` VersionRef int `json:"version"`
Gameinfo *Gameinfo `json:"game"` GameInfo GameInfo `json:"game"`
} }