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

View File

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

View File

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

View File

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

View File

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

View File

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

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,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 {

View File

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

View File

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