OnSyncedUpdate() execution order is non deterministic across unity instances!

Hi,

So due to the way our game works, every player provides their input - then that input is converted into actions on a 'slave' unit which is simulated by all characters.

You can swap slaves at will (not really, but bear with me) - and slaves can be controlled by the AI if you don't own one - so the TrueSync input module is its own TrueSynceBehaviour.

Then the idea is that if you tap a button to take control of a different slave/pawn, the input will be processed, THEN the slave will execute.

Well, this is great and works.... except that TrueSync doesn't guarantee the execution order of its TrueSyncBehaviours. Ideally, we would want all player input truesync objects to be sorted 'first', then the 'player controlled slaves', then finally the AI slaves.

All 3 entities need to be true sync behaviours ( so they can receive OnSyncedStart and OnSyncedUpdate()! ) - but order matters. Right now, on our client, sometimes an AI slave will execute before the player input that takes control of it, sometimes the player slave executes before the player input sent to it (THIS FRAME!!)

So my question is - since determinism is so important to this model - can you add a Priority field to TrueSyncManagedBehaviour or TrueSynceBehaviour so we can ensure the execution order of the game?

I believe this should be trivial to implement and performant, since you only update the list order when a new entity is created, but its done inside the blackbox code for the TrueSyncBehaviour, so I can't modify it myself. Right now we are doing :horrible: hacks to ensure all inputs are collected before running the OnSyncedUpdate() for slaves.

Cheers

Comments

  • Hello @Xelnath, it is a good feature this "priority" property, we have thought about it too. But one thing, the object that calls OnSyncedInput and OnSyncUpdate is the TrueSyncManger, which you have access to it, so you could control the order TrueSyncbehaviours are added and order it by yourself. Did you try this option already?
  • Oh really? Let me take a look. While I was tracing it went into black box code and I stopped following it... let me look harder.
  • Ah, I wanted to add a int called 'Priority' to this class:


    using System.Collections.Generic;

    namespace TrueSync
    {
    public class TrueSyncManagedBehaviour : ITrueSyncBehaviourGamePlay, ITrueSyncBehaviour, ITrueSyncBehaviourCallbacks
    {
    public ITrueSyncBehaviour trueSyncBehavior;
    [AddTracking]
    public bool disabled;
    public TSPlayerInfo localOwner;
    public TSPlayerInfo owner;

    public TrueSyncManagedBehaviour(ITrueSyncBehaviour trueSyncBehavior);

    public static void OnGameEnded(List generalBehaviours, Dictionary> behaviorsByPlayer);
    public static void OnGamePaused(List generalBehaviours, Dictionary> behaviorsByPlayer);
    public static void OnGameStarted(List generalBehaviours, Dictionary> behaviorsByPlayer);
    public static void OnGameUnPaused(List generalBehaviours, Dictionary> behaviorsByPlayer);
    public static void OnPlayerDisconnection(List generalBehaviours, Dictionary> behaviorsByPlayer, byte playerId);
    public void OnGameEnded();
    public void OnGamePaused();
    public void OnGameUnPaused();
    public void OnPlayerDisconnection(int playerId);
    public void OnPreSyncedUpdate();
    public void OnSyncedInput();
    public void OnSyncedStart();
    public void OnSyncedStartLocalPlayer();
    public void OnSyncedUpdate();
    public void SetGameInfo(TSPlayerInfo localOwner, int numberOfPlayers);
    }
    }
    That way it would be as easy as calling Array.Sort() using a very simple comparator - but without that, it will have to be a bit more complex... I'll try it anyways...
  • Xelnath
    Xelnath
    edited July 2017
    It looks like in this case, we can actually just reverse the order of these code blocks:



    TrueSyncInput.CurrentSimulationData = null;

    for (int index = 0, length = allInputData.Count; index < length; index++) {
    InputDataBase playerInputData = allInputData[index];

    if (behaviorsByPlayer.ContainsKey(playerInputData.ownerID)) {
    TrueSyncInput.CurrentSimulationData = (InputData) playerInputData;

    List managedBehavioursByPlayer = behaviorsByPlayer[playerInputData.ownerID];
    for (int index2 = 0, length2 = managedBehavioursByPlayer.Count; index2 < length2; index2++) {
    TrueSyncManagedBehaviour bh = managedBehavioursByPlayer[index2];

    if (bh != null && !bh.disabled) {
    bh.OnSyncedUpdate();
    instance.scheduler.UpdateAllCoroutines();
    }
    }
    }

    TrueSyncInput.CurrentSimulationData = null;
    }

    for (int index = 0, length = generalBehaviours.Count; index < length; index++) {
    TrueSyncManagedBehaviour bh = generalBehaviours[index];


    if (bh != null && !bh.disabled) {
    bh.OnSyncedUpdate();
    instance.scheduler.UpdateAllCoroutines();
    }
    }

    Is this Okay???
  • Turned out this wasn't good enough. I added a new interface called IsPriorityTrueSyncBehaviour with a single int property TrueSyncPriority { get; } - then made every true sync behaviour implement this, then sorted them all after being added using their priority.

    it seems to have helped a bit... but now I am untangling the hacks my engineer put in to make it work the previous way ;x
  • Humm, I hope it works, if you order the same way in every instance (which should be the case using the priorioty thing) you will get the same execution order as well.