func (e *Engine) processRequest(req message.Request) { switch r := req.(type) { case *action.Request: if !e.processActionRequest(r) { req.Done(errors.New("Request has been rejected")) return } if err := e.action.AcceptRequest(r); err != nil { log.Println("Warning: Error while accepting action request:", err) } case entity.Request: if !e.processEntityRequest(r) { req.Done(errors.New("Request has been rejected")) return } if err := e.entity.AcceptRequest(r); err != nil { log.Println("Warning: Error while accepting entity request:", 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) } }