Beispiel #1
0
        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);
                }
            }
        }
Beispiel #2
0
        public void ReqTooManyTest()
        {
            ReArrayIdPool <TestPoolable> pool = new ReArrayIdPool <TestPoolable>(10, 1000,
                                                                                 () => { return(new TestPoolable()); },
                                                                                 (obj) => { obj.Clear(); });

            List <int> ids = new List <int>();

            for (int i = 0; i < 15; i++)
            {
                ids.Add(pool.RequestId());
            }

            for (int i = 0; i < 15; i++)
            {
                Assert.IsFalse(ids[i] == -1);
            }

            for (int i = 0; i < pool.Count; i++)
            {
                pool.Values[i].X += i;
            }

            foreach (int id in ids)
            {
                if (id != -1)
                {
                    Assert.AreEqual(id, pool.Values[pool.IdsToIndices[id]].X);
                }
            }
        }
Beispiel #3
0
        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);
        }
Beispiel #4
0
        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);
        }
Beispiel #5
0
        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);
        }
Beispiel #6
0
        public void OrderedReqAndRemoveTest()
        {
            ReArrayIdPool <TestPoolable> pool = new ReArrayIdPool <TestPoolable>(10, 1000,
                                                                                 () => { return(new TestPoolable()); },
                                                                                 (obj) => { obj.Clear(); }, ordered: true);

            List <int> ids = new List <int>();

            for (int i = 0; i < 10; i++)
            {
                ids.Add(pool.RequestId());
            }

            foreach (int id in ids)
            {
                Assert.IsFalse(id == -1);
            }

            for (int i = 0; i < pool.Count; i++)
            {
                pool.Values[i].X += i;
            }

            foreach (int id in ids)
            {
                Assert.AreEqual(id, pool.Values[pool.IdsToIndices[id]].X);
            }

            for (int i = 5; i < 8; i++)
            {
                pool.ReturnId(ids[i]);                                              // return 5, 6, and 7
            }
            Assert.IsTrue(pool.Count == 7);                                         // down from 10
            Assert.IsTrue(pool.Values[pool.IdsToIndices[ids[9]]].X == 9);           // verify that accessing id9 still works
            Assert.IsTrue(pool.IndicesToIds[pool.Count - 1] == ids[ids.Count - 1]); // verify order was preserved

            // request 3 new structs, to fill back up to 10
            List <int> newids = new List <int>();

            for (int i = 0; i < 3; i++)
            {
                newids.Add(pool.RequestId());
            }

            foreach (int id in newids)
            {
                Assert.IsFalse(id == -1);
            }

            Assert.IsTrue(pool.Count == 10); // back to 10
            foreach (int id in newids)
            {
                Assert.AreEqual(0, pool.Values[pool.IdsToIndices[id]].X); // verify that returned structs were cleared
            }
        }
Beispiel #7
0
        public void ServerAdvance()
        {
            // handle inputs

            /*for (int i = 0; i < Server.PlayerInfos.Count; i++)
             * {
             *  byte pid = Server.PlayerInfos.Values[i].PlayerId;
             *  if (pid == 0)
             *      continue; // server doesn't have inputs
             *
             *  InputChecker(pid);
             * }*/

            for (int d = 0; d < NentDatas.Length; d++)
            {
                ReArrayIdPool <SnapHistory <NentBasic2d, NentStaticBasic2d> > data = NentDatas[d];
                for (int i = 0; i < data.Count; i++)
                {
                    SnapHistory <NentBasic2d, NentStaticBasic2d> h = data.Values[i];

                    if (h.PrevFlag != SnapHistory <NentBasic2d, NentStaticBasic2d> .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
                    if (h.StaticData.Id2 == NENT_PLAYEROBJ)
                    {
                        // store the player entities so we know which entity belongs
                        // to which player
                        PlayerEntityIds[h.StaticData.Id1] = (byte)h.EntityId;

                        // if we have no inputs, do regular advancement
                        h.Shots[h.CurrentIndex] = h.Shots[h.PrevIndex];
                        if (!InputChecker(h, h.StaticData.Id1))
                        {
                            AdvanceLogic(h, 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];
                        AdvanceLogic(h, NetSnapper.TickMSTarget);
                    }

                    Nents.ServerSaveSimIntoCurrent(h);
                }
            }
        }
Beispiel #8
0
        public VirtualSocket(ISocket[] targets, IPEndPoint[] addresses,
                             int maxQueueSize, IPEndPoint fromPoint)
        {
            Targets   = targets;
            Addresses = addresses;
            FromPoint = fromPoint;

            Pool = new ReArrayIdPool <Receipt>(10, maxQueueSize,
                                               PoolCreate, (obj) => { obj.Clear(); });
            WritingReceipt = Pool.Request();
        }
Beispiel #9
0
        // LEAVING THIS DISCUSSION HERE FOR POSTERITY
        // ultimately we decided:
        // this can't be generalized neatly into extrap functions
        // instead, the developer / consumer of netsnapper must
        // define a simulator class that handles these questions

        // so how should the server handle advancement?
        // I was thinking that it should just supply an Action which
        // can then loop over entities and create new snapshots however
        // you desire

        // but are we getting into a mess with client extrapolate (prediction)
        // being a different process than the server's engine calculations?
        // how do we keep the central logic condensed there?

        // I guess a bigger question is: are extrapolation methods sufficient for
        // proper prediction? I guess so if they're written sufficiently
        // (e.g. if you don't include collision detection it might predict runnning
        //  through walls)
        // but maybe the real way to make things orderly is to require a engine-run
        // method from both client and server, and let the consumer game decide
        // what is done, with the understanding that on the client side
        // missing timestamps will be filled in with prediction

        // game can then structure this in a hybrid approach where the same
        // engine methods are called on both sides

        // and we'd have to remove the rollover extrapolation and replace it with
        // something more sophisticated here, right? that's tricky

        // and we need to account for, requesting frames that are between snapshots right?
        // are those just pure-extrapolated or do we need a system that can,
        // given a starting snapshot, calculate everything forward a given amount

        // on the server side, this system could be run and then new snapshots are generated
        // based on the results

        // on the client side, this system is run on advancement to generate new predictive
        // snapshots, and on frame to generate in-between frames

        // right?

        // so what we really need is some kind of advancement scaffold,
        // which has reusable memory for all the snapshots that must be loaded
        // and serves as a sandbox that prediction/calculation can be run in
        // and then the snapshots in there can be either rendered, stored
        // clientside as new silver-flags, or stored & networked by the server
        // as new gold-flags

        // Cleanup
        public void Removed()
        {
            // Called when this snapper is removed

            // salt the earth
            FirstData.Clear();
            SecondData.Clear();

            FirstData  = null;
            SecondData = null;

            FirstEntityIdToInnerId  = null;
            SecondEntityIdToInnerId = null;
        }
Beispiel #10
0
        public Snapper(int firstWindowLength = 64, int secondWindowLength = 64)
        {
            Packer = new TPacker();

            FirstWindowLength  = firstWindowLength;
            SecondWindowLength = secondWindowLength;

            // setup entity data
            FirstData = new ReArrayIdPool <SnapHistory <TSnap, TStatic> >(4, byte.MaxValue + 1,
                                                                          () =>
            {
                return(new SnapHistory <TSnap, TStatic>(firstWindowLength, true));
            },
                                                                          (s) =>
            {
                for (int i = 0; i < s.Shots.Length; i++)
                {
                    Packer.Clear(ref s.Shots[i]);
                }
                s.Clear();
            });

            SecondData = new ReArrayIdPool <SnapHistory <TSnap, TStatic> >(4, ushort.MaxValue + 1,
                                                                           () =>
            {
                return(new SnapHistory <TSnap, TStatic>(secondWindowLength, false));
            },
                                                                           (s) =>
            {
                for (int i = 0; i < s.Shots.Length; i++)
                {
                    Packer.Clear(ref s.Shots[i]);
                }
                s.Clear();
            });

            // setup entityid to innerid maps
            FirstEntityIdToInnerId = new int[8];
            for (int i = 0; i < FirstEntityIdToInnerId.Length; i++)
            {
                FirstEntityIdToInnerId[i] = -1;
            }

            SecondEntityIdToInnerId = new int[8];
            for (int i = 0; i < SecondEntityIdToInnerId.Length; i++)
            {
                SecondEntityIdToInnerId[i] = -1;
            }
        }
Beispiel #11
0
        public Receipt(ReArrayIdPool <Receipt> pool)
        {
            Data     = new byte[UdpClientExtensions.MaxUdpSize];
            Length   = 0;
            EndPoint = (IPEndPoint)UdpClientExtensions.anyV4Endpoint;
            PoolId   = -1;
            Pool     = pool;

            PlayerId       = 0;
            TargetPlayerId = 0;
            MessageId      = 0;
            IsImmediate    = false;

            Processed     = false;
            CanBeReleased = false;
        }
Beispiel #12
0
        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);
        }
Beispiel #13
0
        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);
                }
            }
        }
Beispiel #14
0
        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);
        }
Beispiel #15
0
        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);
        }
Beispiel #16
0
        // 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;
        }
Beispiel #17
0
        // Advance Methods
        public void ClientAdvance(int times, float tickms)
        {
            for (int t = 0; t < times; t++)
            {
                for (int d = 0; d < NentDatas.Length; d++)
                {
                    ReArrayIdPool <SnapHistory <NentBasic2d, NentStaticBasic2d> > data = NentDatas[d];
                    for (int i = 0; i < data.Count; i++)
                    {
                        SnapHistory <NentBasic2d, NentStaticBasic2d> h = data.Values[i];

                        if (h.PrevFlag == SnapHistory <NentBasic2d, NentStaticBasic2d> .FlagEmpty ||
                            h.PrevFlag == SnapHistory <NentBasic2d, NentStaticBasic2d> .FlagDeghosted)
                        {
                            continue; // if the prev flag is empty, this entity does
                        }
                        // not exist yet

                        // check to see if this is one of our player objects,
                        // if so, we resimulate it according to our inputs
                        bool skipAdvancement = false;
                        if (h.StaticData.Id2 == NENT_PLAYEROBJ)
                        {
                            // store the player entities so we know which entity belongs
                            // to which player
                            PlayerEntityIds[h.StaticData.Id1] = (byte)h.EntityId;

                            // we'll only have inputs if it is our player object
                            // since we don't receive inputs for other players
                            if (h.StaticData.Id1 == Server.OurPlayerId)
                            {
                                // if we have no inputs, skip advancement will be false
                                // and we'll do regular simulation on this object (see below)
                                h.Shots[h.CurrentIndex] = h.Shots[h.PrevIndex];
                                skipAdvancement         = InputChecker(h, (byte)h.EntityId);
                            }
                        }

                        if (!skipAdvancement)
                        {
                            if (h.CurrentFlag == SnapHistory <NentBasic2d, NentStaticBasic2d> .FlagGold)
                            {
                                continue; // if we already have a gold from the server,
                            }
                            // there is no purpose in resimulating.

                            // we know the prev snapshot must be gold or silver, or we
                            // wouldn't be here right now. So now we just need to see
                            // what the next snapshot is.

                            // if we have a gold snapshot ahead of us, we can just
                            // interpolate between Prev and Next, which is less costly.
                            // if not, we need to Advance from Prev to Current

                            // check Next
                            if (h.NextFlag != SnapHistory <NentBasic2d, NentStaticBasic2d> .FlagGold)
                            {
                                // we don't have gold ahead of us, so we must
                                // simulate the current snapshot from previous
                                h.Shots[h.CurrentIndex] = h.Shots[h.PrevIndex];
                                AdvanceLogic(h, NetSnapper.TickMSTarget);
                            }
                            else
                            {
                                // we can interpolate since the next flag is gold
                                InterpolateSnapLogic(h);
                            }
                        }

                        // if we have gold we would have already `continue;`d by now
                        // so it's safe to save as silver here
                        Nents.ClientSaveSimIntoCurrent(h);
                    }
                }

                // load next
                NetSnapper.AdvanceSimulateTimestamp();
            }

            // now advance to TickMS
            for (int d = 0; d < NentDatas.Length; d++)
            {
                ReArrayIdPool <SnapHistory <NentBasic2d, NentStaticBasic2d> > data = NentDatas[d];
                for (int i = 0; i < data.Count; i++)
                {
                    SnapHistory <NentBasic2d, NentStaticBasic2d> h = data.Values[i];

                    if (h.PrevFlag == SnapHistory <NentBasic2d, NentStaticBasic2d> .FlagEmpty ||
                        h.PrevFlag == SnapHistory <NentBasic2d, NentStaticBasic2d> .FlagDeghosted)
                    {
                        continue; // if the prev flag is empty, this entity does
                    }
                    // not exist yet

                    if (h.CurrentFlag == SnapHistory <NentBasic2d, NentStaticBasic2d> .FlagGold)
                    {
                        // we have a gold flag, so we can interpolate
                        InterpolateLogic(h, tickms);
                    }
                    else
                    {
                        // otherwise we must simulate
                        AdvanceLogic(h, tickms);
                    }
                }
            }

            // now Current is populated with the latest snapshots
            // adjusted forward to meet the current tickMS
            // this can be rendered by the client
        }
Beispiel #18
0
        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);
        }
Beispiel #19
0
        /// <summary>
        /// Tries to open a UdpSocket, starting with port, and if port is already
        /// in use, incrementing and retrying until it hits maxPort.
        /// </summary>
        /// <param name="port"></param>
        /// <param name="maxPort"></param>
        /// <param name="maxQueueSize"></param>
        /// <param name="netLogger"></param>
        public UdpSocket(int port, int maxPort, int maxQueueSize, NetLogger netLogger)
        {
            // check if port is available
            IPEndPoint[] actives = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners();
            while (true)
            {
                bool found = false;
                for (int i = 0; i < actives.Length; i++)
                {
                    if (actives[i].Port == port)
                    {
                        port++;
                        found = true;
                        break;
                    }
                }

                if (port > maxPort)
                {
                    throw new Exception("Tried to open UDP socket, but first available port '" + port + "' exceeded maximum port '" + maxPort + "'");
                }

                if (!found)
                {
                    break;
                }
            }

            Port = port;

            NetLogger = netLogger;
            UdpClient = new UdpClient(port);

            DirectFromPoint = new IPEndPoint(IPAddress.Loopback, port);

            // important note:
            // the pool here is ordered, which means returning an item is less efficient
            // but it guarantees that the last receipt, which is the empty one being
            // written to, stays at the end of the pool.
            Pool = new ReArrayIdPool <Receipt>(10, maxQueueSize,
                                               PoolCreate, (obj) => { obj.Clear(); });
            WritingReceipt = Pool.Request();

            CancellationTokenSource = new CancellationTokenSource();
            CancellationToken ctoken = CancellationTokenSource.Token;

            Task.Run(async() =>
            {
                if (NetLogger.On)
                {
                    NetLogger.Log("Opening [UDPv6] listener on port " + port + "...");
                }

                while (!ctoken.IsCancellationRequested)
                {
                    try
                    {
                        Receipt rec = await ReceiveAsync();
                        // the receipt, by nature of its existence, goes into the queue.
                    }
                    catch (Exception e)
                    {
                        if (NetLogger.On)
                        {
                            NetLogger.Error("UdpClient listener encountered exception on " + port, e);
                        }
                    }
                }

                if (NetLogger.On)
                {
                    NetLogger.Log("Closing listener on port " + port + "...");
                }

                try
                {
                    UdpClient.Close();
                    UdpClient.Dispose();
                }
                catch
                {
                }
            });
        }
Beispiel #20
0
        // 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
                }
            }
        }