public byte PrepGhostSecond(ushort entityid, ushort timestamp) { // if ``, can't possibly belong to this snapper if (entityid >= SecondEntityIdToInnerId.Length) { return(0); } // if ``, this snapper doesn't have this entity int innerid = SecondEntityIdToInnerId[entityid]; if (innerid == -1) { return(0); } SnapHistory <TSnap, TStatic> h = SecondData.Values[SecondData.IdsToIndices[innerid]]; TempWriteHistory = h; TempWriteIndex = h.FindIndex(timestamp); if (TempWriteIndex < 0) { return(0); } return(Packer.PrepPackFull( h.Shots[TempWriteIndex], h.StaticData, out PackInfo)); }
private bool UnpackDelta(ReArrayIdPool <SnapHistory <TSnap, TStatic> > data, int innerid, byte[] blob, int blobstart, int blobcount, ushort timestamp, ushort basisTimestamp) { SnapHistory <TSnap, TStatic> sh = data.Values[data.IdsToIndices[innerid]]; int index = sh.FindIndex(timestamp); if (index < 0 || index >= sh.Shots.Length) { return(false); // out of bounds } int basisIndex = sh.FindIndex(basisTimestamp); if (basisIndex < 0 || basisIndex >= sh.Shots.Length) { return(false); // out of bounds } Packer.UnpackDelta(ref sh.Shots[index], sh.Shots[basisIndex], blob, blobstart, blobcount); sh.Timestamps[index] = timestamp; sh.Flags[index] = SnapHistory <TSnap, TStatic> .FlagGold; // handle flag rollover Rollover(sh, index, timestamp); return(true); }
private bool UnpackDelta(ReArrayIdPool <SnapHistory <TSnap, TStatic> > data, int innerid, byte[] blob, int blobstart, int blobcount, ushort timestamp, ushort basisTimestamp) { SnapHistory <TSnap, TStatic> sh = data.Values[data.IdsToIndices[innerid]]; int index = sh.FindIndex(timestamp); if (index < 0 || index >= sh.Shots.Length) { return(false); // out of bounds } int basisIndex = sh.FindIndex(basisTimestamp); if (basisIndex < 0 || basisIndex >= sh.Shots.Length) { return(false); // out of bounds } Packer.UnpackDelta(ref sh.Shots[index], sh.Shots[basisIndex], blob, blobstart, blobcount); sh.Timestamps[index] = timestamp; sh.Flags[index] = SnapHistory <TSnap, TStatic> .FlagGold; // ask the server to resimulate from this timestamp // to ensure our silver guesses get updated NetSnapper.RequestResimulate(timestamp); return(true); }
public bool ServerAddEntitySecond(TSnap initalSnap, TStatic staticData, out ushort eid, bool ghostAll = true) { if (!NetSnapper.ServerRequestEntitySecond(EntityType, out eid)) { return(false); // NetSnapper says: no entity space available } if (SecondEntityIdToInnerId.Length <= eid) { ExpandSecondEntityIdMap(eid); } SnapHistory <TSnap, TStatic> h = SecondData.Request(); h.StaticData = staticData; h.EntityId = eid; h.Timestamps[0] = NetSnapper.CurrentTime; h.Flags[0] = SnapHistory <TSnap, TStatic> .FlagGold; h.LeadingIndex = 0; h.Shots[0] = initalSnap; SecondEntityIdToInnerId[eid] = h.PoolId; if (ghostAll) { // ghost the new entity to all players NetSnapper.ServerSendGhostAllSecond(eid, EntityType); } return(true); }
public void ServerAdvance() { for (int d = 0; d < NentDatas.Length; d++) { ReArrayIdPool <SnapHistory <TSnap, TStatic> > data = NentDatas[d]; for (int i = 0; i < data.Count; i++) { SnapHistory <TSnap, TStatic> h = data.Values[i]; if (h.PrevFlag != SnapHistory <TSnap, TStatic> .FlagGold) { continue; // if the prevflag is not gold, } // the entity does not exist at this timestamp // so we don't bother simulating it yet // now we advance the snapshot forward to the current point in // time, and then save it h.Shots[h.CurrentIndex] = h.Shots[h.PrevIndex]; Advancer.AdvanceLogic(AdvancerConfig, h, ref h.Shots[h.CurrentIndex], NetSnapper.TickMSTarget); Nents.ServerSaveSimIntoCurrent(h); } } }
private bool Deghost(ReArrayIdPool <SnapHistory <TSnap, TStatic> > data, int innerid, ushort timestamp) { SnapHistory <TSnap, TStatic> sh = data.Values[data.IdsToIndices[innerid]]; int index = sh.FindIndex(timestamp); if (index < 0 || index >= sh.Shots.Length) { return(false); // out of bounds } sh.Flags[index] = SnapHistory <TSnap, TStatic> .FlagDeghosted; // scroll forward and deghost subsequent timestamps! // what to do if deghost and ghost arrive out of order? // simple: by only overwriting SILVER flags, we ensure that deghosting // will not overwrite fresh / more recent data int nindex = index + 1; ushort nextTime = timestamp; while (true) { if (nindex == sh.Shots.Length) { nindex = 0; } if (nextTime == ushort.MaxValue) { nextTime = 0; } else { nextTime++; } // we can only rollover onto silver flags if (sh.Flags[nindex] != SnapHistory <TSnap, TStatic> .FlagSilver && sh.Flags[nindex] != SnapHistory <TSnap, TStatic> .FlagEmpty) { break; } // can only rollover onto subsequent timestamps if (sh.Timestamps[nindex] != nextTime) { break; } // now we're ready to roll sh.Flags[nindex] = SnapHistory <TSnap, TStatic> .FlagDeghosted; nindex++; } return(true); }
// Simulant Helpers public void ServerSaveSimIntoCurrent(SnapHistory <TSnap, TStatic> ent) { ent.Flags[ent.CurrentIndex] = SnapHistory <TSnap, TStatic> .FlagGold; if (ent.First) { NetSnapper.ServerSendDeltaAllFirst((byte)ent.EntityId, EntityType); } else { NetSnapper.ServerSendDeltaAllSecond(ent.EntityId, EntityType); } }
public bool PrepDeltaSecond(ushort entityid, ushort timestamp, ushort basisTimestamp, out byte len) { len = 0; // if ``, can't possibly belong to this snapper if (entityid >= SecondEntityIdToInnerId.Length) { return(false); } // if ``, this snapper doesn't have this entity int innerid = SecondEntityIdToInnerId[entityid]; if (innerid == -1) { return(false); } SnapHistory <TSnap, TStatic> h = SecondData.Values[SecondData.IdsToIndices[innerid]]; int index = h.FindIndex(timestamp); if (index == -1) { return(false); } int basisIndex = h.FindIndex(basisTimestamp); if (basisIndex == -1) { return(false); } TempWriteHistory = h; TempWriteIndex = index; len = Packer.PrepPackDelta( h.Shots[index], h.Shots[basisIndex], out PackInfo); return(true); }
private bool UnpackFull(ReArrayIdPool <SnapHistory <TSnap, TStatic> > data, int innerid, byte[] blob, int blobstart, int blobcount, ushort timestamp) { SnapHistory <TSnap, TStatic> sh = data.Values[data.IdsToIndices[innerid]]; int index = sh.FindIndex(timestamp); if (index < 0) { return(false); // out of bounds-- too far in future or past } Packer.UnpackFull(ref sh.Shots[index], ref sh.StaticData, blob, blobstart, blobcount); sh.Timestamps[index] = timestamp; sh.Flags[index] = SnapHistory <TSnap, TStatic> .FlagGold; // handle flag rollover Rollover(sh, index, timestamp); return(true); }
public void ServerAdvance() { for (int d = 0; d < NentDatas.Length; d++) { ReArrayIdPool<SnapHistory<TSnap, TStatic>> data = NentDatas[d]; for (int i = 0; i < data.Count; i++) { SnapHistory<TSnap, TStatic> h = data.Values[i]; if (h.PrevFlag != SnapHistory<TSnap, TStatic>.FlagGold) continue; // if the prevflag is not gold, // the entity does not exist at this timestamp // so we don't bother simulating it yet // now we advance the snapshot forward to the current point in // time, and then save it byte ownerPid = Advancer.GetInputPlayer(h.Shots[h.CurrentIndex], h.StaticData); if (ownerPid != 0) { // if we have no inputs, do regular advancement h.Shots[h.CurrentIndex] = h.Shots[h.PrevIndex]; if (!InputChecker(h, ref h.Shots[h.CurrentIndex], ownerPid, NetSnapper.TickMSTarget)) { Advancer.AdvanceLogic(AdvancerConfig, h, ref h.Shots[h.CurrentIndex], NetSnapper.TickMSTarget); } } else { // if this were client, we'd check the flag of the next. // as it is, just create the next from the current h.Shots[h.CurrentIndex] = h.Shots[h.PrevIndex]; Advancer.AdvanceLogic(AdvancerConfig, h, ref h.Shots[h.CurrentIndex], NetSnapper.TickMSTarget); } Nents.ServerSaveSimIntoCurrent(h); } } }
private bool UnpackFull(ReArrayIdPool <SnapHistory <TSnap, TStatic> > data, int innerid, byte[] blob, int blobstart, int blobcount, ushort timestamp) { SnapHistory <TSnap, TStatic> sh = data.Values[data.IdsToIndices[innerid]]; int index = sh.FindIndex(timestamp); if (index < 0) { return(false); // out of bounds-- too far in future or past } Packer.UnpackFull(ref sh.Shots[index], ref sh.StaticData, blob, blobstart, blobcount); sh.Timestamps[index] = timestamp; sh.Flags[index] = SnapHistory <TSnap, TStatic> .FlagGold; // ask the server to resimulate from this timestamp // to ensure our silver guesses get updated NetSnapper.RequestResimulate(timestamp); return(true); }
// Prime Logic // Advancement occurs every tick public void Advance(ushort currentTime) { CurrentTime = currentTime; // when we advance, push every leading edge forward for (int i = 0; i < FirstData.Count; i++) { SnapHistory <TSnap, TStatic> sh = FirstData.Values[i]; ushort last = sh.Timestamps[sh.LeadingIndex]; ushort next = last == ushort.MaxValue ? (ushort)0 : (ushort)(last + 1); int startIndex = sh.LeadingIndex; sh.LeadingIndex++; if (sh.LeadingIndex == sh.Shots.Length) { sh.LeadingIndex = 0; } if (sh.Timestamps[sh.LeadingIndex] != next) { // we haven't received the next snap yet, so destroy it and // ensure that it is empty for the simulator sh.Timestamps[sh.LeadingIndex] = next; sh.Flags[sh.LeadingIndex] = SnapHistory <TSnap, TStatic> .FlagEmpty; // note: we don't need to actually call Clear on the snapshot // since the fact that it's marked with an empty flag means // the simulator should avoid using it. // if the previous snapshot was deghosted, // deghost this one as well. if (sh.Flags[startIndex] == SnapHistory <TSnap, TStatic> .FlagDeghosted) { sh.Flags[sh.LeadingIndex] = SnapHistory <TSnap, TStatic> .FlagDeghosted; } } } // same logic as above but for second data // only reason we don't use a method for this is having that many method // calls seems like a waste. for (int i = 0; i < SecondData.Count; i++) { SnapHistory <TSnap, TStatic> sh = SecondData.Values[i]; ushort last = sh.Timestamps[sh.LeadingIndex]; ushort next = last == ushort.MaxValue ? (ushort)0 : (ushort)(last + 1); int startIndex = sh.LeadingIndex; sh.LeadingIndex++; if (sh.LeadingIndex == sh.Shots.Length) { sh.LeadingIndex = 0; } if (sh.Timestamps[sh.LeadingIndex] != next) { sh.Timestamps[sh.LeadingIndex] = next; sh.Flags[sh.LeadingIndex] = SnapHistory <TSnap, TStatic> .FlagEmpty; if (sh.Flags[startIndex] == SnapHistory <TSnap, TStatic> .FlagDeghosted) { sh.Flags[sh.LeadingIndex] = SnapHistory <TSnap, TStatic> .FlagDeghosted; } } } // now check our destruct queues if (FirstDestructQueue.Count > 0) { for (int i = FirstDestructQueue.Count - 1; i >= 0; i--) { ushort destructTime = FirstDestructQueueTime[i]; int dist = 0; if (currentTime < destructTime) { dist = (ushort.MaxValue - destructTime) + currentTime; } else { dist = currentTime - destructTime; } if (dist < FirstWindowLength) { break; } NetSnapper.ServerDestructFirst(FirstDestructQueue[i]); FirstDestructQueue.RemoveAt(i); FirstDestructQueueTime.RemoveAt(i); } } if (SecondDestructQueue.Count > 0) { for (int i = SecondDestructQueue.Count - 1; i >= 0; i--) { ushort destructTime = SecondDestructQueueTime[i]; int dist = 0; if (currentTime < destructTime) { dist = (ushort.MaxValue - destructTime) + currentTime; } else { dist = currentTime - destructTime; } if (dist < FirstWindowLength) { break; } NetSnapper.ServerDestructSecond(SecondDestructQueue[i]); SecondDestructQueue.RemoveAt(i); SecondDestructQueueTime.RemoveAt(i); } } }
private void Rollover(SnapHistory <TSnap, TStatic> sh, int index, ushort timestamp) { // this whole idea has to be reworked // I guess an issue is that one of our key assumptions // is that the indices will always be populated with timestamps // so we need to have some kind of rollover // but doing the extrap action here is not good // 1. it's not real simulated // 2. why not do interp when available? // one option would be to do simple extrap/interp here // or even just copy the same snapshot over and over // and then, when we get an older timestamp, flag the simulator // as "needs rollback calculation" (ONLY IF WE ACTUALLY HAVE ANY // SILVER SNAPSHOTS AHEAD OF THIS TIMESTAMP) // then, at the end of each processing, if the simulator has // this flag set, it will run again from whatever the oldest time // flagged was (need to track the timestamp of each flagging as well) // this will generate new silver snapshots, and they'll be accurate to boot. // RE: THIS DISCUSSION // we decided to do the "resimulate flagging" approach // so the way this works now is that if we receive a timestamp, // and there's unknowns in front of it, // the system gets asked to resimulate from that timestamp int nindex = index + 1; ushort nextTime = timestamp; if (nindex == sh.Shots.Length) { nindex = 0; } if (nextTime == ushort.MaxValue) { nextTime = 0; } else { nextTime++; } // we can only rollover onto silver flags if (sh.Flags[nindex] != SnapHistory <TSnap, TStatic> .FlagSilver && sh.Flags[nindex] != SnapHistory <TSnap, TStatic> .FlagEmpty) { return; } // can only rollover onto subsequent timestamps if (sh.Timestamps[nindex] != nextTime) { return; } // if we get here, then we hit a silver or an empty that must be filled out // tell the snap system to resimulate NetSnapper.RequestResimulate(timestamp); // VVVV OLD VVVV /* * // flags need to act a certain way to allow us to have * // snapshots roll over when no changes are needed * // e.g.: * // 1 = from server (GOLD STANDARD) * // 2 = roll over, this occurs when no snapshot is received * // we assume shot is unchanged and use it again for next * // timestamp * // now imagine we have some snaps: * // aaaaabb * // 1222212 * // ^ and here we receive a new snapshot c * // we SCROLL FORWARD and replace all 2s with the new snapshot: * // aacccbb * // 1212212 * // this way we can let client continue to scroll forward but * // also correct itself properly * int nindex = index + 1; * ushort nextTime = timestamp; * int rolls = 1; * while (true) * { * if (nindex == sh.Shots.Length) * nindex = 0; * * if (nextTime == ushort.MaxValue) * nextTime = 0; * else * nextTime++; * * // we can only rollover onto silver flags * if (sh.Flags[nindex] != SnapHistory<T>.FlagSilver && sh.Flags[nindex] != SnapHistory<T>.FlagEmpty) * break; * * // can only rollover onto subsequent timestamps * if (sh.Timestamps[nindex] != nextTime) * break; * * // now we're ready to roll * ExtrapolateAction(ref sh.Shots[nindex], sh.Shots[index], TickMS * rolls); * rolls++; * * nindex++; * }*/ }
private int Ghost(ReArrayIdPool <SnapHistory <TSnap, TStatic> > data, ushort entityid, byte[] blob, int blobstart, int blobcount, ushort timestamp) { SnapHistory <TSnap, TStatic> sh = data.Request(); sh.EntityId = entityid; sh.LeadingIndex = 0; sh.Timestamps[0] = CurrentTime; // setup the rest of the future timestamps ushort itime = sh.Timestamps[0]; for (int i = 1; i <= sh.Shots.Length / 2; i++) { if (itime == ushort.MaxValue) { itime = 0; } else { itime++; } sh.Timestamps[i] = itime; } // and the past itime = sh.Timestamps[0]; for (int i = 0; i < sh.Shots.Length / 2; i++) { if (itime == 0) { itime = ushort.MaxValue; } else { itime--; } sh.Timestamps[sh.Shots.Length - 1 - i] = itime; } int expIndex = sh.FindIndex(timestamp); if (expIndex == -1) { // in this case, we need to just abandon the snapshot we // received. This can obviously create issues since we'll // need a full snapshot again. // however, we still unpack it in full, so the static data // can be constructed Packer.UnpackFull(ref sh.Shots[0], ref sh.StaticData, blob, blobstart, blobcount); sh.Flags[0] = SnapHistory <TSnap, TStatic> .FlagEmpty; } else { // if the snapshot we received has a space in the current window, // so just unpack into there Packer.UnpackFull(ref sh.Shots[expIndex], ref sh.StaticData, blob, blobstart, blobcount); sh.Timestamps[expIndex] = timestamp; sh.Flags[expIndex] = SnapHistory <TSnap, TStatic> .FlagGold; } // return the innerid return(sh.PoolId); }
// Prime Logic // Advancement occurs every tick public void Advance(ushort currentTime) { CurrentTime = currentTime; // when we advance, push every leading edge forward for (int i = 0; i < FirstData.Count; i++) { SnapHistory <TSnap, TStatic> sh = FirstData.Values[i]; ushort last = sh.Timestamps[sh.LeadingIndex]; ushort next = last == ushort.MaxValue ? (ushort)0 : (ushort)(last + 1); int startIndex = sh.LeadingIndex; sh.LeadingIndex++; if (sh.LeadingIndex == sh.Shots.Length) { sh.LeadingIndex = 0; } if (sh.Timestamps[sh.LeadingIndex] != next) { // we haven't received the next snap yet, so destroy it and // ensure that it is empty for the simulator sh.Timestamps[sh.LeadingIndex] = next; sh.Flags[sh.LeadingIndex] = SnapHistory <TSnap, TStatic> .FlagEmpty; // note: we don't need to actually call Clear on the snapshot // since the fact that it's marked with an empty flag means // the simulator should avoid using it. // if the previous snapshot was deghosted, // deghost this one as well. if (sh.Flags[startIndex] == SnapHistory <TSnap, TStatic> .FlagDeghosted) { sh.Flags[sh.LeadingIndex] = SnapHistory <TSnap, TStatic> .FlagDeghosted; } } } // same logic as above but for second data // only reason we don't use a method for this is having that many method // calls seems like a waste. for (int i = 0; i < SecondData.Count; i++) { SnapHistory <TSnap, TStatic> sh = SecondData.Values[i]; ushort last = sh.Timestamps[sh.LeadingIndex]; ushort next = last == ushort.MaxValue ? (ushort)0 : (ushort)(last + 1); int startIndex = sh.LeadingIndex; sh.LeadingIndex++; if (sh.LeadingIndex == sh.Shots.Length) { sh.LeadingIndex = 0; } if (sh.Timestamps[sh.LeadingIndex] != next) { sh.Timestamps[sh.LeadingIndex] = next; sh.Flags[sh.LeadingIndex] = SnapHistory <TSnap, TStatic> .FlagEmpty; if (sh.Flags[startIndex] == SnapHistory <TSnap, TStatic> .FlagDeghosted) { sh.Flags[sh.LeadingIndex] = SnapHistory <TSnap, TStatic> .FlagDeghosted; } } } }
// predicts forward a set number of ms public void ClientPredictMS(float delta) { for (int d = 0; d < NentDatas.Length; d++) { ReArrayIdPool<SnapHistory<TSnap, TStatic>> data = NentDatas[d]; for (int i = 0; i < data.Count; i++) { SnapHistory<TSnap, TStatic> h = data.Values[i]; // if the impulse time has moved up, move up the entity's to match if (h.HasImpulse && ((NetSnapper.ClientImpulseTime >= 500 && h.ImpulseTimestamp < NetSnapper.ClientImpulseTime && h.ImpulseTimestamp >= NetSnapper.ClientImpulseTime - 500) || (NetSnapper.ClientImpulseTime < 500 && h.ImpulseTimestamp < NetSnapper.ClientImpulseTime) || (NetSnapper.ClientImpulseTime < 500 && h.ImpulseTimestamp >= ushort.MaxValue - 500))) h.ImpulseTimestamp = NetSnapper.ClientImpulseTime; if (h.PrevFlag == SnapHistory<TSnap, TStatic>.FlagEmpty || h.PrevFlag == SnapHistory<TSnap, TStatic>.FlagDeghosted) continue; // if the prev flag is empty, this entity does // not exist yet // if the entity has impulse, we may need to predict // if the current timestamp is after the impulse timestamp if (h.HasImpulse && ((NetSnapper.SimulateTimestamp >= h.ImpulseTimestamp && !(NetSnapper.SimulateTimestamp >= ushort.MaxValue - 500 && h.ImpulseTimestamp <= 500)) || (NetSnapper.SimulateTimestamp <= 500 && h.ImpulseTimestamp >= ushort.MaxValue - 500))) { // first, check if we have an input // we could only have an input if we are the owner of this object byte inputPid = Advancer.GetInputPlayer(h.ImpulseShot, h.StaticData); if (inputPid == Server.OurPlayerId) { // now check if we have any inputs // if so, process them if (!InputChecker(h, ref h.ImpulseShot, inputPid, delta)) { // if not, do a normal advance Advancer.AdvanceLogic(AdvancerConfig, h, ref h.ImpulseShot, delta); } } else { // if we couldn't have an input, just do a normal advance Advancer.AdvanceLogic(AdvancerConfig, h, ref h.ImpulseShot, delta); } // now check if we need to blend if (!h.PermanentImpulse && ((NetSnapper.SimulateTimestamp >= h.BlendTimestamp && !(NetSnapper.SimulateTimestamp >= ushort.MaxValue - 500 && h.BlendTimestamp <= 500)) || (NetSnapper.SimulateTimestamp <= 500 && h.BlendTimestamp >= ushort.MaxValue - 500))) { // we're in the Blend Zone // find the snapshot that we would be displaying, if this were not // an impulse. TSnap blendTarget = h.Shots[h.CurrentIndex]; if (h.NextFlag == SnapHistory<TSnap, TStatic>.FlagGold || h.NextFlag == SnapHistory<TSnap, TStatic>.FlagSilver) { Advancer.InterpMSLogic(h, ref blendTarget, delta, NetSnapper.TickMSTarget); } else { Advancer.AdvanceLogic(AdvancerConfig, h, ref blendTarget, delta); } // determine the blend factor int blendTicks = NetSnapper.SimulateTimestamp; if (NetSnapper.SimulateTimestamp <= 500 && h.BlendTimestamp >= ushort.MaxValue - 500) { blendTicks = (ushort.MaxValue - h.BlendTimestamp) + 1 + NetSnapper.SimulateTimestamp; } else { blendTicks -= h.BlendTimestamp; } h.ActiveBlendFactor = blendTicks * h.BlendFactor + h.BlendFactor * (delta/NetSnapper.TickMSTarget); if (h.ActiveBlendFactor > 1) { h.ActiveBlendFactor = 1; // special consideration: // if the impulse time equals the simulate time (in other words, // this is our first timestamp of the predictive window), and // our blend factor is already over 1, then we are already // back to being in-sync with the server, so we can remove impulse // from this entity. if (NetSnapper.ClientImpulseTime == NetSnapper.SimulateTimestamp) { h.HasImpulse = false; } } // now blend the shots together Advancer.BlendLogic(h, ref h.ImpulseShot, blendTarget, h.ActiveBlendFactor); } // finally, continue so we don't hit the interp logic below continue; } // if it's not impulse, we need to interp or extrap as normal // so we know the prev is good for interp, but is the next? // if the next is also good for interp, we'll just interp h.ImpulseShot = h.Shots[h.CurrentIndex]; if (h.NextFlag == SnapHistory<TSnap, TStatic>.FlagGold || h.NextFlag == SnapHistory<TSnap, TStatic>.FlagSilver) { Advancer.InterpMSLogic(h, ref h.ImpulseShot, delta, NetSnapper.TickMSTarget); } else { // otherwise, do extrapolation Advancer.AdvanceLogic(AdvancerConfig, h, ref h.ImpulseShot, delta); } // note: we don't save this, it is just for rendering } } }
public void ClientSaveSimIntoCurrent(SnapHistory <TSnap, TStatic> ent) { ent.Flags[ent.CurrentIndex] = SnapHistory <TSnap, TStatic> .FlagSilver; }
private int Ghost(ReArrayIdPool <SnapHistory <TSnap, TStatic> > data, ushort entityid, byte[] blob, int blobstart, int blobcount, ushort timestamp) { SnapHistory <TSnap, TStatic> sh = data.Request(); sh.EntityId = entityid; Packer.UnpackFull(ref sh.Shots[0], ref sh.StaticData, blob, blobstart, blobcount); sh.Timestamps[0] = timestamp; sh.Flags[0] = SnapHistory <TSnap, TStatic> .FlagGold; sh.LeadingIndex = 0; // now, figure how close we are to current time // under normal conditions, we should be receiving snapshots // very close to the CurrentTime int expIndex = sh.FindIndex(CurrentTime); // however, if the expIndex is -1, it means the snapshot // we just received is outside the current window // (because CurrentTime relative to it is outside the window) if (expIndex == -1) { // in this case, we need to just abandon the snapshot we // received. This can obviously create issues since we'll // need a full snapshot again. sh.Timestamps[0] = CurrentTime; sh.Flags[0] = SnapHistory <TSnap, TStatic> .FlagEmpty; } else { // in this case, we're within the window, so let's // just set the leading index properly. sh.LeadingIndex = expIndex; sh.Timestamps[sh.LeadingIndex] = CurrentTime; } // setup the rest of the future timestamps ushort itime = sh.Timestamps[0]; for (int i = 1; i <= sh.Shots.Length / 2; i++) { if (itime == ushort.MaxValue) { itime = 0; } else { itime++; } sh.Timestamps[i] = itime; } // and the past itime = sh.Timestamps[0]; for (int i = 0; i < sh.Shots.Length / 2; i++) { if (itime == 0) { itime = ushort.MaxValue; } else { itime--; } sh.Timestamps[sh.Shots.Length - 1 - i] = itime; } // return the innerid return(sh.PoolId); }
// predicts forward a whole tick // this returns true if all impulse entities have conf snapshots // indicating that we can turn up the impulseTimestamp public bool ClientPredictTick(ushort confTimestamp) { ushort prevConfTimestamp = confTimestamp; if (prevConfTimestamp == 0) prevConfTimestamp = ushort.MaxValue; else prevConfTimestamp--; bool noMissingImpulse = true; for (int d = 0; d < NentDatas.Length; d++) { ReArrayIdPool<SnapHistory<TSnap, TStatic>> data = NentDatas[d]; for (int i = 0; i < data.Count; i++) { SnapHistory<TSnap, TStatic> h = data.Values[i]; if (h.PrevFlag == SnapHistory<TSnap, TStatic>.FlagEmpty || h.PrevFlag == SnapHistory<TSnap, TStatic>.FlagDeghosted) continue; // if the prev flag is empty, this entity does // not exist yet // if the entity has impulse, we may need to predict // if the current timestamp is after the impulse timestamp if (h.HasImpulse && ((NetSnapper.SimulateTimestamp >= h.ImpulseTimestamp && !(NetSnapper.SimulateTimestamp >= ushort.MaxValue - 500 && h.ImpulseTimestamp <= 500)) || (NetSnapper.SimulateTimestamp <= 500 && h.ImpulseTimestamp >= ushort.MaxValue - 500))) { // cases: // 0. if SimulateTimestamp is before h.ImpulseTimestamp, don't // predict, do normal interp // 1. if this entity has a gold snapshot in SimulateTimestamp + I, // then don't predict; use the server result // 2. if not, advance a predicted result // case 0 is accounted for by the if statement above // case 1 int confIndex = h.FindIndex(confTimestamp); if (confIndex != -1 && h.Flags[confIndex] == SnapHistory<TSnap, TStatic>.FlagGold && h.ImpulseTimestamp == NetSnapper.SimulateTimestamp) { // in this case, our job is easy, we have a server confirmed snap // so we just use that h.ImpulseShot = h.Shots[confIndex]; //NetSnapper.Server.NetLogger.Log(NetSnapper.SimulateTimestamp + " / " + confTimestamp + " F"); } else { // case 2 // in this case, we must predict based on the current ImpulseShot // because we got here, we are missing a conf for this impulse // so we can't move up the NetSnapper's ImpulseTimestamp noMissingImpulse = false; if (h.ImpulseTimestamp == NetSnapper.SimulateTimestamp) { // special consideration: if this is our first impulse, // we must populate ImpulseShot // if we have a confirmed shot for the previous timestamp, // we should use that // if we don't, this must be our first prediction, so we // can simply use the previous snapshot int prevConfIndex = h.FindIndex(prevConfTimestamp); if (prevConfIndex != -1 && h.Flags[prevConfIndex] == SnapHistory<TSnap, TStatic>.FlagGold) { h.ImpulseShot = h.Shots[prevConfIndex]; //NetSnapper.Server.NetLogger.Log(h.ImpulseTimestamp + " / " + prevConfTimestamp + " C"); } else { h.ImpulseShot = h.Shots[h.PrevIndex]; //NetSnapper.Server.NetLogger.Log(h.ImpulseTimestamp + " / " + prevConfTimestamp + " P"); } } // first, check if we have an input // we could only have an input if we are the owner of this object byte inputPid = Advancer.GetInputPlayer(h.ImpulseShot, h.StaticData); if (inputPid == Server.OurPlayerId) { // now check if we have any inputs // if so, process them if (!InputChecker(h, ref h.ImpulseShot, inputPid, NetSnapper.TickMSTarget)) { // if not, do a normal advance Advancer.AdvanceLogic(AdvancerConfig, h, ref h.ImpulseShot, NetSnapper.TickMSTarget); } } else { // if we couldn't have an input, just do a normal advance Advancer.AdvanceLogic(AdvancerConfig, h, ref h.ImpulseShot, NetSnapper.TickMSTarget); } } } // even if we did impulse above, we still want to interp // because we may need to create silver snapshots to use for // blending or impulse later // if we have a gold from the server, we don't need to do anything // because the current shot is already good if (h.CurrentFlag == SnapHistory<TSnap, TStatic>.FlagGold) continue; // (you might notice from the following, that we resimulate silver // flags [client guesses] each tick; this way they continue to update // their guesses if new server info arrives) // if not, we need to create a silver for the current shot // we know the previous is gold or silver already, since we checked // for empty or ghost way back above // so we know the prev is good for interp, but is the next? // if the next is also good for interp, we'll just interp h.Shots[h.CurrentIndex] = h.Shots[h.PrevIndex]; if (h.NextFlag == SnapHistory<TSnap, TStatic>.FlagGold || h.NextFlag == SnapHistory<TSnap, TStatic>.FlagSilver) { Advancer.InterpTickLogic(h); } else { // otherwise, do extrapolation Advancer.AdvanceLogic(AdvancerConfig, h, ref h.Shots[h.CurrentIndex], NetSnapper.TickMSTarget); } // save our guess snapshot as a new silver Nents.ClientSaveSimIntoCurrent(h); } } return noMissingImpulse; }
// returns false if we have no inputs private bool InputChecker(SnapHistory<TSnap, TStatic> h, ref TSnap snap, byte pid, float maxms) { TInput[] actions = Input.GetPlayerInputs(pid, out int index, out int count, out ushort[] timestamps, out float[] tickms); if (count == 0) return false; ushort simstamp = NetSnapper.SimulateTimestamp; // timestamps are ordered from oldest --> latest // loop through seeking our current timestamp float lastms = 0; TInput lastAction = new TInput(); bool hasLastAction = false; for (int i = 0; i < count; i++) { // if timestamps[index] is later than our timestamp, stop // if timestamps[index] is earlier than our timestamp, keep going // if timestamps[index] is equal to our timestamp, process it if (timestamps[index] == simstamp) { // if we are past the maxms we should process to, then stop if (tickms[index] >= maxms) { break; } // process it if (hasLastAction) { Advancer.InputLogic(AdvancerConfig, lastAction, h, ref snap, pid, tickms[index] - lastms); } hasLastAction = true; lastms = tickms[index]; lastAction = actions[index]; } else if (simstamp >= ushort.MaxValue / 2) { if (timestamps[index] > simstamp || timestamps[index] < simstamp - (ushort.MaxValue / 2)) { // later break; } } else { if (timestamps[index] > simstamp && timestamps[index] < simstamp + (ushort.MaxValue / 2)) { // later break; } } index++; if (index >= actions.Length) index -= actions.Length; } // process the last action, now that we're done if (hasLastAction) { // we use the tickrate here bbecause this action stretches until the end // of the tick, since there are no subsequent actions in this tick Advancer.InputLogic(AdvancerConfig, lastAction, h, ref snap, pid, maxms - lastms); } return true; }