From f512df73dd811af0323f5c032c45d95b6b4af6aa Mon Sep 17 00:00:00 2001 From: Jordan Orelli Date: Fri, 6 Nov 2020 23:45:32 +0000 Subject: [PATCH] computing frame deltas --- internal/sim/door.go | 1 + internal/sim/player.go | 13 ++-- internal/sim/room.go | 5 +- internal/sim/world.go | 6 ++ internal/wire/frame.go | 144 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 160 insertions(+), 9 deletions(-) diff --git a/internal/sim/door.go b/internal/sim/door.go index 64b17f3..a7a44b6 100644 --- a/internal/sim/door.go +++ b/internal/sim/door.go @@ -56,6 +56,7 @@ func (d *door) exec(w *world, r *room, p *player, seq int) result { p.Error("failed to add player avatar to tile %v", t) } dest.addPlayer(p) + p.fullSync = true return result{} } diff --git a/internal/sim/player.go b/internal/sim/player.go index e4a3dbb..2061593 100644 --- a/internal/sim/player.go +++ b/internal/sim/player.go @@ -13,12 +13,12 @@ import ( type player struct { *blammo.Log - //*room - name string - outbox chan wire.Response - pending *Request - avatar *entity - stop chan bool + name string + outbox chan wire.Response + pending *Request + avatar *entity + stop chan bool + fullSync bool } func (p *player) start(c chan Request, conn *websocket.Conn, r *room) { @@ -167,6 +167,7 @@ func (p *player) update(dt time.Duration) {} type spawnPlayer struct{} func (s spawnPlayer) exec(w *world, r *room, p *player, seq int) result { + p.fullSync = true e := entity{ ID: <-w.nextID, Glyph: '@', diff --git a/internal/sim/room.go b/internal/sim/room.go index 3fc0e50..9c8d1c1 100644 --- a/internal/sim/room.go +++ b/internal/sim/room.go @@ -10,8 +10,9 @@ type room struct { *blammo.Log name string math.Rect - tiles []tile - players map[string]*player + tiles []tile + players map[string]*player + lastFrame wire.Frame } func (r *room) allEntities() map[int]wire.Entity { diff --git a/internal/sim/world.go b/internal/sim/world.go index e7671da..35e6fcc 100644 --- a/internal/sim/world.go +++ b/internal/sim/world.go @@ -284,6 +284,12 @@ func (w *world) tick(d time.Duration) { Players: r.playerAvatars(), } + delta := r.lastFrame.Diff(frame) + if delta != nil { + w.Info("%s delta: %s", r.name, delta) + } + r.lastFrame = frame + for _, p := range r.players { p.send(wire.Response{Body: frame}) } diff --git a/internal/wire/frame.go b/internal/wire/frame.go index 4a8cfd1..c482a72 100644 --- a/internal/wire/frame.go +++ b/internal/wire/frame.go @@ -1,6 +1,13 @@ package wire -import "github.com/jordanorelli/astro-domu/internal/math" +import ( + "bytes" + "fmt" + "strconv" + "strings" + + "github.com/jordanorelli/astro-domu/internal/math" +) type Frame struct { RoomName string `json:"room_name"` @@ -11,6 +18,141 @@ type Frame struct { func (Frame) NetTag() string { return "frame" } +func (f Frame) Diff(next Frame) *Delta { + var delta Delta + + if f.RoomSize != next.RoomSize { + rs := next.RoomSize + delta.RoomSize = &rs + } + + for id, e := range next.Entities { + if old, ok := f.Entities[id]; !ok { + // a new entity + delta.addEntity(e) + } else { + // an existing entity + if e != old { + delta.addEntity(e) + } + } + } + + for id, _ := range f.Entities { + // entity removed + if _, ok := next.Entities[id]; !ok { + delta.nullEntity(id) + } + } + + for name, id := range next.Players { + if oldID, ok := f.Players[name]; !ok { + // a new player + delta.addPlayer(name, id) + } else { + // an existing player + if oldID != id { + delta.addPlayer(name, id) + } + } + } + + for name, _ := range f.Players { + if _, ok := next.Players[name]; !ok { + delta.nullPlayer(name) + } + } + if !delta.IsEmpty() { + return &delta + } + return nil +} + +type Delta struct { + RoomSize *math.Rect `json:"room_size,omitempty"` + Entities map[int]*Entity `json:"entities,omitempty"` + Players map[string]*int `json:"players,omitempty"` +} + +func (d Delta) String() string { + first := true + var buf bytes.Buffer + fmt.Fprint(&buf, "Δ{") + if d.RoomSize != nil { + fmt.Fprintf(&buf, "size(%d,%d@%d,%d)", d.RoomSize.Origin.X, d.RoomSize.Origin.Y, d.RoomSize.Width, d.RoomSize.Height) + first = false + } + if len(d.Entities) > 0 { + if !first { + buf.WriteString(",") + } + buf.WriteString("entities<") + parts := make([]string, 0, len(d.Entities)) + for id, e := range d.Entities { + if e == nil { + parts = append(parts, fmt.Sprintf("-%d", id)) + } else { + parts = append(parts, strconv.Itoa(id)) + } + } + buf.WriteString(strings.Join(parts, ",")) + buf.WriteString(">") + first = false + } + if len(d.Players) > 0 { + if !first { + buf.WriteString(",") + } + buf.WriteString("players<") + parts := make([]string, 0, len(d.Players)) + for name, id := range d.Players { + if id == nil { + parts = append(parts, fmt.Sprintf("-%s", name)) + } else { + parts = append(parts, fmt.Sprintf("+%s", name)) + } + } + buf.WriteString(strings.Join(parts, ",")) + buf.WriteString(">") + first = false + } + return buf.String() +} + +func (Delta) NetTag() string { return "delta" } + +func (d *Delta) addEntity(e Entity) { + if d.Entities == nil { + d.Entities = make(map[int]*Entity) + } + d.Entities[e.ID] = &e +} + +func (d *Delta) nullEntity(id int) { + if d.Entities == nil { + d.Entities = make(map[int]*Entity) + } + d.Entities[id] = nil +} + +func (d *Delta) addPlayer(name string, id int) { + if d.Players == nil { + d.Players = make(map[string]*int) + } + d.Players[name] = &id +} + +func (d *Delta) nullPlayer(name string) { + if d.Players == nil { + d.Players = make(map[string]*int) + } + d.Players[name] = nil +} + +func (d Delta) IsEmpty() bool { + return d.RoomSize == nil && len(d.Entities) == 0 && len(d.Players) == 0 +} + func init() { Register(func() Value { return new(Frame) }) }