Search Unity

Need advice creating extensible rollback pattern for NGO

Discussion in 'Netcode for GameObjects' started by O_man0, Mar 11, 2023.

  1. O_man0

    O_man0

    Joined:
    May 29, 2022
    Posts:
    1
    Hey all, I'm trying to create a rollback framework for my game. Most of the examples I see online only show rollback for a single object and the code is entirely encapsulated in that NetworkBehaviour, but I want to have multiple objects be 'Rollbackable' (for instance, moving terrain that can affect player movement should be rollbackable). I want all these Rollbackable components to be defined in a similar way, but of course these Rollbackables have different structs to define their input and state payloads, so it is messy to define. I'm fairly new to C# so i've been reading on ways to accomplish this but I've just gone down this rabbit hole reading about interfaces, <T> in interfaces and methods, 'object' as a type, 'dynamic', boxing and unboxing, etc... I'm not really sure how to proceed and hoping to waste as little time on possible on solutions that just won't work.

    So my hope is to make an interface IRollbackable that components extend from, so...
    public class Player : NetworkBehaviour, IRollbackable {...}

    IStateful is basically for classes that will never trigger a rollback but want their state tracked over time (like moving terrain)
    It is still rough but its has been thinking about this system has been blocking me for a week now, so I could use some advice.

    Some specific/additional questions:
    • This uses 'object' type like a mfer... what is the exact risk in terms of performance/safety?
    • Is making a generic methodology for this too much to ask for?
    • if this IRollbackable is attached to NetworkBehaviours, is it OK to move them to a simulation scene? It won't remove them from the network or have any adverse side effects?
    • if there exists some straightforward framework for this, NGO or otherwise, please point me in that direction. THANK YOU!!!

    Code (CSharp):
    1.  
    2.     public interface IStateful {
    3.         bool RollingBack{ get; set; }
    4.         object[] StateBuffer{ get; set;} // does this work?
    5.         object[] InputBuffer{ get; set;} // does this work?
    6.         void bufferizeInput(object input, object[] inputBuffer){
    7.             inputBuffer[NetcodeGlobals.tick % NetcodeGlobals.bufferSize] = input;
    8.         }
    9.         void bufferizeState(object state, object[] stateBuffer){
    10.             stateBuffer[NetcodeGlobals.tick % NetcodeGlobals.bufferSize] = state;
    11.         }
    12.  
    13.         void applyInputToState(object input, object state);
    14.  
    15.         void subscribeToRollbackManager(int objectId){
    16.             RollbackManager.Instance.statefulMap.Add(objectId, this);
    17.         }
    18.         void unsubscribeFromRollbackManager(int objectId){
    19.             RollbackManager.Instance.statefulMap.Remove(objectId);
    20.  
    21.         }
    22.     }
    23.  
    24.     public interface IRollbackable : IStateful{
    25.         bool hasIncorrectState(object currState, object recievedState);
    26.  
    27.         new void subscribeToRollbackManager(int objectId){
    28.             RollbackManager.Instance.rollbackableMap.Add(objectId, this);
    29.         }
    30.         new void unsubscribeFromRollbackManager(int objectId){
    31.             RollbackManager.Instance.rollbackableMap.Remove(objectId);
    32.         }
    33.  
    34.         void triggerRollback(int rollbackTick){
    35.             RollbackManager.Instance.toRollback = true;
    36.             RollbackManager.Instance.rollbackTick = Mathf.Min(rollbackTick, RollbackManager.Instance.rollbackTick);
    37.         }
    38.     }
    39.    
    40.     public class RollbackManager : MonoBehaviour
    41.     {
    42.         // Singleton pattern
    43.         public static RollbackManager Instance { get; private set; }
    44.         private void Awake()
    45.         {
    46.             // If there is an instance, and it's not me, delete myself.
    47.             if (Instance != null && Instance != this)
    48.             {
    49.                 Destroy(this);
    50.             }
    51.             else
    52.             {
    53.                 Instance = this;
    54.             }
    55.             DontDestroyOnLoad(gameObject);
    56.         }
    57.  
    58.         public bool toRollback = false;
    59.         public int rollbackTick = 0;
    60.         public Dictionary<int, IStateful> statefulMap;
    61.         public Dictionary<int, IRollbackable> rollbackableMap;
    62.         // --> This is subscribed to by IRollbackable
    63.  
    64.  
    65.         public void rewind(int tick){
    66.             foreach (var stateful in statefulMap){
    67.                 stateful.Value.RollingBack = true;
    68.                 // move objects to new scene
    69.  
    70.             }
    71.  
    72.             foreach (var rollbackable in rollbackableMap){
    73.                 rollbackable.Value.RollingBack = true;
    74.                 // move objects to new scene
    75.             }
    76.  
    77.             /* psuedo-code
    78.             while (rollbackedTick < currentTick)
    79.             {
    80.                 for (all objects in new scene){
    81.                     obj.applyInputToState();
    82.                 }
    83.                 PhysicsScene.simulate(oneTick);
    84.                 for (all objects in new scene){
    85.                     bufferizeState();
    86.                     bufferizeInput();
    87.                 }
    88.                 rollbackedTick += 1;
    89.             }
    90.             */
    91.             // rollback complete, move objects back to primary scene
    92.  
    93.         }
    94.  
    95.         void FixedUpdate(){
    96.             if (toRollback){
    97.                 rewind(rollbackTick);
    98.                 toRollback = false;
    99.             }
    100.         }
    101.     }
     
  2. lavagoatGG

    lavagoatGG

    Joined:
    Apr 16, 2022
    Posts:
    229
    I am not sure moving the actual NetworkBehaviour objects to a new scene will work. Maybe you should try to clone them in to a new scene as local objects (maybe even only on the server) and then after the rollback is complete sync the network object's state.