func (e *Engine) moveEntities(t message.AbsoluteTick) { for _, ent := range e.entity.List() { req, collidesWith := e.mover.UpdateEntity(ent, t) // [GAME-SPECIFIC] Destroy balls when they collide if collidesWith != nil && ent.Type == game.BallEntity { req := entity.NewDeleteRequest(t, ent.Id) if err := e.entity.AcceptRequest(req); err != nil { panic(err) // This shouldn't happen } // If the ball collides with a player, decrease his health if collidesWith, ok := collidesWith.(*message.Entity); ok && collidesWith.Type == game.PlayerEntity { sender := ent.Attributes[game.SenderAttr].(message.EntityId) if sender != collidesWith.Id { health := collidesWith.Attributes[game.HealthAttr].(game.Health) updated := entity.New() updated.Attributes[game.HealthAttr] = health - 1 diff := message.NewEntityDiff() diff.Attributes = true req := entity.NewUpdateRequest(t, updated, diff) if err := e.entity.AcceptRequest(req); err != nil { panic(err) // This shouldn't happen } } } continue } if req != nil { // Let's assume mover already did all security checks if err := e.entity.AcceptRequest(req); err != nil { // This should not fail panic(err) } } } }
func (e *Engine) executeTick(currentTick message.AbsoluteTick) { entityFrontend := e.entity.Frontend() actionFrontend := e.action.Frontend() // Process requests from clients minTick := currentTick - message.MaxRewind if message.MaxRewind > currentTick { minTick = 0 } acceptedMinTick := currentTick accepted := list.New() for { var req message.Request // Get last request noMore := false select { case req = <-entityFrontend.Creates: case req = <-entityFrontend.Updates: case req = <-entityFrontend.Deletes: case req = <-actionFrontend.Executes: default: noMore = true } if noMore { break } t := req.GetTick() if t == message.InvalidTick { req.Done(errors.New("Invalid tick")) log.Println("Warning: Dropped request, invalid tick") continue } if t < minTick { req.Done(errors.New("Request is too old")) log.Println("Warning: Dropped request, too old", currentTick-t) continue } if t > currentTick { req.Done(errors.New("Request is in the future")) log.Println("Warning: Dropped request, in the future", currentTick-t) continue } if t < acceptedMinTick { acceptedMinTick = t } // Append request to the list, keeping it ordered inserted := false for e := accepted.Front(); e != nil; e = e.Next() { r := e.Value.(message.Request) if r.GetTick() > req.GetTick() { accepted.InsertBefore(req, e) inserted = true break } } if !inserted { accepted.PushBack(req) } } if accepted.Len() > 0 { //log.Println("Accepted", accepted.Len(), "requests from clients") } // Initiate lag compensation if necessary if acceptedMinTick < currentTick { log.Println("Back to the past!", currentTick, acceptedMinTick) e.terrain.Rewind(e.terrain.GetTick() - acceptedMinTick) e.entity.Rewind(e.entity.GetTick() - acceptedMinTick) } // Redo all deltas until now entDeltas := e.entity.Deltas() trnDeltas := e.terrain.Deltas() entEl := entDeltas.FirstAfter(acceptedMinTick) trnEl := trnDeltas.FirstAfter(acceptedMinTick) var ed *entity.Delta var td *terrain.Delta for entEl != nil || trnEl != nil { if entEl != nil { ed = entEl.Value.(*entity.Delta) } else { ed = nil } if trnEl != nil { td = trnEl.Value.(*terrain.Delta) } else { td = nil } // TODO: replace terain history by actions history if ed == nil || (td != nil && ed.GetTick() >= td.GetTick()) { // Process terrain delta trnEl = trnEl.Next() // Compute new entities positions just before the terrain change e.moveEntities(td.GetTick() - 1) // Redo terrain change e.terrain.Redo(td) } if td == nil || (ed != nil && ed.GetTick() <= td.GetTick()) { // Process entity delta // Do not redo deltas not triggered by the user // These are the ones that will be computed again if !ed.Requested() { entDeltas.Remove(entEl) entEl = entEl.Next() continue } entEl = entEl.Next() // Compute new entities positions e.moveEntities(ed.GetTick()) // Accept new requests at the correct tick for el := accepted.Front(); el != nil; el = el.Next() { req := el.Value.(message.Request) if req.GetTick() >= ed.GetTick() { break } e.processRequest(req) accepted.Remove(el) } e.processRequest(ed.Request()) } } // Accept requests whose tick is > last delta tick for el := accepted.Front(); el != nil; el = el.Next() { req := el.Value.(entity.Request) e.moveEntities(req.GetTick()) e.processRequest(req) } // Compute new entities positions e.moveEntities(currentTick) // Destroy entities that are not alive anymore // TODO: history support for _, ent := range e.entity.List() { if val, ok := ent.Attributes[game.TicksLeftAttr]; ok { ttl := val.(game.TicksLeft) if ttl == 0 { // Destroy entity err := e.entity.AcceptRequest(entity.NewDeleteRequest(currentTick, ent.Id)) if err != nil { // This shouldn't fail panic(err) } } else { ent.Attributes[game.TicksLeftAttr] = ttl - 1 } } } // Cleanup e.entity.Cleanup(currentTick) e.action.Cleanup(currentTick) // DEBUG: print all entites log.Printf("Tick %v finished, entities:\n", currentTick) for _, ent := range e.entity.List() { log.Printf("entity=%+v position=%+v speed=%+v", ent, ent.Position, ent.Speed) } }