sschmid / Entitas

Entitas is a super fast Entity Component System (ECS) Framework specifically made for C# and Unity
MIT License
7.08k stars 1.11k forks source link

Networking and TickSystem #931

Closed mchamplain closed 4 years ago

mchamplain commented 4 years ago

Hi, I'm trying to wrap my head around how to handle networking and Tickrate concepts.

I'm currently experimenting with that, both in a console application (pure c#) and in Unity, so in my console application I execute systems in a while loop with a Thread.Sleep(1) so that I don't hog the CPU and in Unity it's the usual Update method from a GameController monobehaviour. The goal is to be able to run a dedicated server, but also let the user host a game simply inside the Unity build.

I have a unique CurrentTickComponent and an IExecute TickSystem that replace the current tick 30 times per seconds. I'm adding the deltatime on each iteration and when it's >= to my tickrate I increment the tick. When running from the console program I see that I usually skip ~15 iteration before the tick gets incremented. The delta on each tick vary between 0.033 and 0.035. (please comment if there's a better way to do that ticksystem)


    [Game, Unique, Event(EventTarget.Self)]
    public class CurrentTickComponent : IComponent
    {
        public ulong value;
        public float delta;
    }

    public class IncrementTickSystem : IExecuteSystem
    {
        private readonly Contexts _contexts;
        private float _timeToNextTick;
        public static readonly float TickFrequency = 1/30f;

        public IncrementTickSystem(Contexts contexts)
        {
            _contexts = contexts;
        }

        public void Execute()
        {
            _timeToNextTick += _contexts.meta.timeService.instance.GetDeltaTime();
            _timeToNextTick = UpdateTick(_timeToNextTick);
        }

        private float UpdateTick(float timeToNextTick)
        {
            if (timeToNextTick < TickFrequency) return timeToNextTick;

            _contexts.game.ReplaceCurrentTick(_contexts.game.currentTick.value + 1, timeToNextTick);

            timeToNextTick -= TickFrequency;
            return timeToNextTick;
        }
    }

I tried to simulate longer processing time, so I randomly change the Thread.Sleep() time on some frame. What I'm seeing is that when it happen, my ticksystem doesn't skip any frame and increment the tick on each frame until it's able to catch-up. My thinking is that if there's a lag spike, it should then try to process actions as fast as possible until it's back to normal.

I think this makes sense but I would like to get input from ppl that have experience with tick+networking.

(simplified example) If player1 do the action TakeItem#123 from a crate, that action is sent to the server, the server add the action to it's context, on next loop it take the oldest action and mark it as "current action", then some inventory system will remove the item from the crate and assign it to the player, So if 2 players end up doing the same action at the same time, there's still only one that can be processed and the 2nd player will get an error that the item is not available anymore.

I kinda feel like I need 2 different tick system, one that increment on every loop, allowing the server to process things as fast as possible, and another for network stuff to have a somewhat constant flow of data between the clients and server.

Does it make sense to have multiple ticksystem with possibly different tickrate?

For example (with easy numbers), the "core" tickrate, let's say it does 1 iteration per second. Another "networking" tickrate that would run every 5 seconds. So this would allow the core to process a bunch of actions before having to send update to clients 🤔 So if the server get actions from 3 different players in the same packet Iteration 1: process the first action Iteration 2: process 2nd action iteration 3: process 3rd action iteration 4: nothing iteration 5: trigger networking tick that sends update to clients.

How about adding a 3rd tickrate for stuff that regenerate over time, might be easier to deal with tick than with deltatime? But keeping that tickrate independent from the main loop and the network updates.

Any thoughts welcomed 😄 🤕