Announcement

Collapse
No announcement yet.

Problem with ServerTravel (Seamless Travel) and LinkedReplicationInfo

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Problem with ServerTravel (Seamless Travel) and LinkedReplicationInfo

    I'm facing a problem with the replication chain of owned LinkedReplicationInfo objects. I created a very basic mutator which will add a LinkedReplicationInfo for only the specific player (to store some player specific data etc.). This is working well for dedicated servers, listen servers and instant action if the mutator is activated on game creation.

    SeamLessTravelProblemLPRI.uc
    Code:
    class SeamLessTravelProblemLPRI extends UTLinkedReplicationInfo;
    
    /** used to replicate all settings at once.
     *  This is false if the setup is done, so any variable can be replicated seperately at will
     */
    var bool bInitialSetupDone;
    
    /** Used for proper replication with client/server functions */
    var repnotify PlayerController PlayerOwner;
    
    var delegate<OnReplicationCallback> InitialSetupDelegate;
    
    delegate OnReplicationCallback(SeamLessTravelProblemLPRI PRI);
    
    replication
    {
        if(bNetInitial && (Role == ROLE_Authority))
            PlayerOwner;
    }
    
    simulated event ReplicatedEvent(name VarName)
    {
        `Log(name$"::ReplicatedEvent - VarName:"@VarName);
    
        super.ReplicatedEvent(VarName);
    
        if (VarName == 'PlayerOwner') {
            `Log(name$"::ReplicatedEvent - PlayerOwner:"@PlayerOwner);
            SetOwner(PlayerOwner);
    
            // This method will request the initial setup
            // This is somehow needed for seamless travel where replication of LinkedRepInfos works differently and not working at all
            //ServerInitialSetup();
        }
    }
    
    simulated function PostBeginPlay()
    {
        `log(name$"::PostBeginPlay");
        super.PostBeginPlay();
    }
    
    simulated function Destroyed()
    {
        `log(name$"::Destroyed");
        super.Destroyed();
    }
    
    reliable client event ClientReplicateSetup(/* MyStruct Setup */)
    {
        `log(name$"::ClientReplicateSetup");
        ReplicateSetup(/* Setup */);
    }
    
    reliable server function ServerInitialSetup()
    {
        `log(name$"::ServerInitialSetup");
    
        if (bInitialSetupDone)
        {
            `log(name$"::ServerInitialSetup - Already initialized. Return.");
            return;
        }
    
        if (InitialSetupDelegate != none)
        {
            `log(name$"::ServerInitialSetup - Call delegate");
            OnReplicationCallback = InitialSetupDelegate;
            OnReplicationCallback(self);
        }
    }
    
    /**
     * Server function
     */
    function ServerReplicateSetup(/* MyStruct Setup */)
    {
        `log(name$"::ServerReplicateSetup");
        ReplicateSetup(/* Setup */);
        ClientReplicateSetup(/* Setup */);
    }
    
    simulated event ReplicateSetup(/* MyStruct Setup */)
    {
        `log(name$"::ReplicateSetup");
    }
    
    DefaultProperties
    {
        bOnlyRelevantToOwner=True
    }
    SeamLessTravelProblemMutator.uc
    Code:
    class SeamLessTravelProblemMutator extends UTMUtator;
    
    function PostBeginPlay()
    {
        `Log(name$"::PostBeginPlay");
        Super.PostBeginPlay();
    }
    
    event Destroyed()
    {
        `Log(name$"::Destroyed");
        Super.Destroyed();
    }
    
    function NotifyLogout(Controller Exiting)
    {
        `Log(name$"::NotifyLogout - Exiting:"@Exiting);
        RemovePRIFrom(PlayerController(Exiting));
        super.NotifyLogout(Exiting);
    }
    
    function NotifyLogin(Controller NewPlayer)
    {
        `Log(name$"::NotifyLogin - NewPlayer:"@NewPlayer);
        Super.NotifyLogin(NewPlayer);
        ConditionallyAddPRIFor(PlayerController(NewPlayer));
    }
    
    function SeamLessTravelProblemLPRI ConditionallyAddPRIFor(PlayerController PC)
    {
        local UTPlayerReplicationInfo UTPRI;
        local SeamLessTravelProblemLPRI LPRI;
        local UTLinkedReplicationInfo LastLinked;
    
    
        `Log(name$"::ConditionallyAddPRIFor - PC:"@PC);
    
        if (PC == none)
        {
            `Log(name$"::ConditionallyAddPRIFor - No PC. Return");
            return none;
        }
    
        UTPRI = UTPlayerReplicationInfo(PC.PlayerReplicationInfo);
        if ( UTPRI != None && !GetLPRI(UTPRI, LPRI))
        {
            `Log(name$"::ConditionallyAddPRIFor - Unable to find LinkedPRI for"@UTPRI$". Create a new one");
    
            // spawn a rep info
            LPRI = Spawn(class'SeamLessTravelProblemLPRI', PC);
            if (LPRI != none)
            {
                `Log(name$"::ConditionallyAddPRIFor - Set Player owner");
                LPRI.SetOwner(PC);
                LPRI.PlayerOwner = PC;
            }
    
            // add to linked list
            if (UTPRI.CustomReplicationInfo != none) {
                LastLinked = UTPRI.CustomReplicationInfo;
                while(LastLinked.NextReplicationInfo != none) {
                    LastLinked = LastLinked.NextReplicationInfo;
                }
                LastLinked.NextReplicationInfo = LPRI;
            } else {
                UTPRI.CustomReplicationInfo = LPRI;
            }
    
            // WORKAROUND
            // At this point, we setup a delegate which will be called after replication of PlayerOwner 
            //LPRI.InitialSetupDelegate = InitPRI;
            // instead of calling it directly ...
            InitPRI(LPRI); // init the values and delegates etc.
    
        } else {
            `Log(name$"::ConditionallyAddPRIFor - No UT PRI.");
        }
    
        `Log(name$"::ConditionallyAddPRIFor - LPRI:"@LPRI);
        return LPRI;
    }
    
    function RemovePRIFrom(PlayerController PC)
    {
        local UTPlayerReplicationInfo UTPRI;
        local UTLinkedReplicationInfo LastLinked;
        local UTLinkedReplicationInfo LPRI;
    
        `Log(name$"::RemovePRIFrom - PC:"@PC);
    
        if (PC == none)
            return;
    
        UTPRI = UTPlayerReplicationInfo(PC.PlayerReplicationInfo);
        if ( UTPRI != None )
        {
            if (UTPRI.CustomReplicationInfo != none)
            {
                if (SeamLessTravelProblemLPRI(UTPRI.CustomReplicationInfo) != none)
                {
                    `Log(name$"::RemovePRIFrom - Remove Custom LinkedPRI");
                    LPRI = UTPRI.CustomReplicationInfo;
                    UTPRI.CustomReplicationInfo = LPRI.NextReplicationInfo;
                    LPRI.Destroy();
                }
                else if (SeamLessTravelProblemLPRI(UTPRI.CustomReplicationInfo.NextReplicationInfo) != none)
                {
                    `Log(name$"::RemovePRIFrom - Remove 1st LinkedPRI in list");
                    LPRI = UTPRI.CustomReplicationInfo.NextReplicationInfo;
                    UTPRI.CustomReplicationInfo.NextReplicationInfo = LPRI.NextReplicationInfo;
                    LPRI.Destroy();
                }
                else
                {
                    `Log(name$"::RemovePRIFrom - Check list of Custom LinkedPRI");
                    LastLinked = UTPRI.CustomReplicationInfo;
                    do {
                        `Log(name$"::RemovePRIFrom - LastLinked:"@LastLinked);
                        `Log(name$"::RemovePRIFrom - LastLinked.NextReplicationInfo:"@LastLinked.NextReplicationInfo);
                        if (LastLinked.NextReplicationInfo != none  && SeamLessTravelProblemLPRI(LastLinked.NextReplicationInfo) !=  none)
                        {
                            `Log(name$"::RemovePRIFrom - Remove");
                            LPRI = LastLinked.NextReplicationInfo;
                            LastLinked.NextReplicationInfo = LPRI.NextReplicationInfo;
                            LPRI.Destroy();
                        } else {
                            LastLinked = LastLinked.NextReplicationInfo;
                        }
                    } until (LastLinked == none || LastLinked.NextReplicationInfo == none);
                }
            }
        }
    }
    
    function InitPRI(SeamLessTravelProblemLPRI PRI)
    {
        /* local MyStruct Setup; */
        `Log(name$"::InitPRI - PRI:"@PRI);
    
        if (PRI.bInitialSetupDone)
        {
            `log(name$"::InitPRI - Already initialized. Return.");
            return;
        }
        
        // set for replication after setup
        PRI.bInitialSetupDone = true;
    
        PRI.ServerReplicateSetup(/* Setup */);
    }
    
    function bool GetLPRI(UTPlayerReplicationInfo UTPRI, out SeamLessTravelProblemLPRI LPRI)
    {
        local UTLinkedReplicationInfo LastLinked;
        
        LastLinked = UTPRI.CustomReplicationInfo;
    
        // Get LPRI of linked list
        if (LastLinked != none)
        {
            do
            {
                LPRI = SeamLessTravelProblemLPRI(LastLinked);
                LastLinked = (LastLinked.NextReplicationInfo != none ? LastLinked.NextReplicationInfo : none);
            } until (LPRI != none || LastLinked == none);
        }
    
        return (LPRI != none);
    }
    
    // only called after the mutator is already added and the map is changed afterwards
    function GetSeamlessTravelActorList(bool bToEntry, out array<Actor> ActorList)
    {
        local int i;
    
        `Log(name$"::GetSeamlessTravelActorList - bToEntry:"@bToEntry,,'POCClientLPRI');
    
        for(i=0; i<ActorList.Length; i++)
        {
            `Log(name$"::GetSeamlessTravelActorList - "$i@ActorList[i],,'POCClientLPRI');
        }
    
        super.GetSeamlessTravelActorList(bToEntry, ActorList);
    }
    
    DefaultProperties
    {
    }
    If the game is created and the mutator was added, both functions ClientReplicateSetup and ReplicateSetup is called on the clientside. However, if the game is created without the/any mutator, and the mutator is added afterwards with WebAdmin (which is using WorldInfo::ServerTravel(true)), the mutator will get added, the Linked RepInfo will get properly created on the client side. The client gets the replication of the variable trough the event (ReplicatedEvent); it sets the (Player)-Owner (so the object is in the Owner chain which will eventually trigger the function replication) but it never calls replication functions.

    Problem:
    No ClientReplicateSetup and ReplicateSetup log entry will occur if the mutator is added to the ServerTravel url. So it never gets executed and the client misses some features.
    I added a workaround. This is currently commented out. This will call a server function if the variable "PlayerOwner" is replicated.

    Does someone knows/has a implentation to allow proper replication without such workaround. It must be a timing problem. I noticed that ReplicatedEvent is only called once on the client's side but ServerInitialSetup is called 3 times on the server's end running on the same machine (no delay). But the mutator (code above) will not work online as well if the mutator is added within server travel.

    Is there any flag i can set, to change the behaviour of the replication?

    My current setup was:
    UT3 v2.1
    Dedicated server:
    Code:
    ut3.exe server DM-Deck?game=UTGame.UTTeamGame?MinNetPlayers=0?Numplay=0 -log
    Client: logged player profile connecting to 127.0.0.1
    Both on the same machine.

    #2
    Seamless travel doesn't work with custom content. It's broke. That's what spawned the UT3 fixer. It was a mutator that turned seamless travel off so that servers were able to run custom content.

    Comment


      #3
      I know UT3Fixer solves problems by disabling seamless travel. My problem does not relate to objects/actors which need to be persistent on SeamlessTravel. The problem is adding the mutator with WebAdmin. The mutator gets loaded and does not properly replicate something, so the "reliable client" methods aren't called. The only method to successfully add this type of mutator is to shutdown the server, and restart the server with the mutator applied to the URL (?mutator=SeamLessTravelProblem.SeamLessTravelProb lemMutator).

      I added the workaround, which initiate a somehow 2 way handshake so the method will be called after a valid request. I'm still unsure why the normal method is not working. The problem should also apply to stock content if LinkedReplicationInfo is used (which is only intended to add mod compability).

      Comment


        #4
        http://udn.epicgames.com/Three/NetworkingOverview.html
        Unlike replicated variables, replicated function calls are sent to the remote machine immediately when they are called, and they are always replicated regardless of bandwidth.
        As I suspected, it looks like it is a timing problem. But I don't know why this is working well with a pre-added mutator and not working at all with a server-travel-mutator.

        Comment


          #5
          I think there is no flag to set to replicate the owner properly so replication functions are called on ServerTravel (Seamless Travel). The previous workaround is not working in ListenServer (on local player) and standalone games as the PlayerOwner will not get replicated there.
          I ended up using something like this:

          SeamLessTravelProblemLPRI.uc
          Code:
          class SeamLessTravelProblemLPRI extends UTLinkedReplicationInfo;
          
          /** used to replicate all settings at once.
           *  This is false if the setup is done, so any variable can be replicated seperately at will
           */
          var bool bInitialSetupDone;
          
          /** Used for proper replication with client/server functions */
          var repnotify PlayerController PlayerOwner;
          
          var delegate<OnReplicationCallback> InitialSetupDelegate;
          
          replication
          {
              if(bNetInitial && (Role == ROLE_Authority))
                  PlayerOwner;
          }
          
          delegate OnReplicationCallback(SeamLessTravelProblemLPRI PRI);
          
          simulated event ReplicatedEvent(name VarName)
          {
              super.ReplicatedEvent(VarName);
          
              if (VarName == 'PlayerOwner') {
                  SetOwner(PlayerOwner);
          
                  // This method will request the initial setup
                  // This is somehow needed for seamless travel where replication of LinkedRepInfos works differently and not working at all
                  ServerInitialSetup();
              }
          }
          
          reliable client event ClientReplicateSetup(/* MyStruct Setup */)
          {
              ReplicateSetup(/* Setup */);
          }
          
          reliable server function ServerInitialSetup()
          {
              if (bInitialSetupDone)
                  return;
          
              if (InitialSetupDelegate != none)
              {
                  OnReplicationCallback = InitialSetupDelegate;
                  OnReplicationCallback(self);
              }
          }
          
          /**
           * Server function
           */
          function ServerReplicateSetup(/* MyStruct Setup */)
          {
              ReplicateSetup(/* Setup */);
              ClientReplicateSetup(/* Setup */);
          }
          
          simulated event ReplicateSetup(/* MyStruct Setup */)
          {
          }
          
          DefaultProperties
          {
              bOnlyRelevantToOwner=True
          }
          SeamLessTravelProblemMutator.uc
          Code:
          class SeamLessTravelProblemMutator extends UTMUtator;
          
          function NotifyLogout(Controller Exiting)
          {
              RemovePRIFrom(PlayerController(Exiting));
              super.NotifyLogout(Exiting);
          }
          
          function NotifyLogin(Controller NewPlayer)
          {
              Super.NotifyLogin(NewPlayer);
              ConditionallyAddPRIFor(PlayerController(NewPlayer));
          }
          
          function SeamLessTravelProblemLPRI ConditionallyAddPRIFor(PlayerController PC)
          {
              local UTPlayerReplicationInfo UTPRI;
              local SeamLessTravelProblemLPRI LPRI;
              local UTLinkedReplicationInfo LastLinked;
          
              if (PC == none)
                  return none;
          
              UTPRI = UTPlayerReplicationInfo(PC.PlayerReplicationInfo);
              if ( UTPRI != None && !GetLPRI(UTPRI, LPRI))
              {
                  // spawn a rep info
                  LPRI = Spawn(class'SeamLessTravelProblemLPRI', PC);
                  if (LPRI != none)
                  {
                      // add to linked list
                      LPRI.NextReplicationInfo = UTPRI.CustomReplicationInfo;
                      UTPRI.CustomReplicationInfo = LPRI;
                      
                      // It's important to set delegate before the engine attempts to replicate the variables
                      // which will trigger the additional replication
                      if ((WorldInfo.NetMode == NM_DedicatedServer) || (WorldInfo.NetMode == NM_ListenServer && PC.RemoteRole == ROLE_AutonomousProxy))
                      {
                          // At this point, we setup a delegate which will be called after replication of PlayerOwner 
                          LRI.UpdateSettingsDelegate = UpdateSettings;
                          // instead of calling it directly ...
                          //UpdateSettings(UTPRI); // init the values and delegates etc.
                      }
          
                      LPRI.SetOwner(PC);
                      LPRI.PlayerOwner = PC;
          
                      // WORKAROUND
                      if ((WorldInfo.NetMode == NM_Standalone) || (WorldInfo.NetMode != NM_DedicatedServer && PC.RemoteRole == ROLE_SimulatedProxy))
                      {
                           // calling it directly on local machine for local player
                          UpdateSettings(UTPRI); // init the values and delegates etc.
                      }
          
                  }
              }
          
              return LPRI;
          }
          
          function RemovePRIFrom(PlayerController PC)
          {
              local UTPlayerReplicationInfo UTPRI;
              local UTLinkedReplicationInfo LastLinked;
              local UTLinkedReplicationInfo LPRI;
          
              if (PC == none)
                  return;
          
              UTPRI = UTPlayerReplicationInfo(PC.PlayerReplicationInfo);
              if ( UTPRI != None )
              {
                  if (UTPRI.CustomReplicationInfo != none)
                  {
                      if (SeamLessTravelProblemLPRI(UTPRI.CustomReplicationInfo) != none)
                      {
                          LPRI = UTPRI.CustomReplicationInfo;
                          UTPRI.CustomReplicationInfo = LPRI.NextReplicationInfo;
                          LPRI.Destroy();
                      }
                      else if (SeamLessTravelProblemLPRI(UTPRI.CustomReplicationInfo.NextReplicationInfo) != none)
                      {
                          LPRI = UTPRI.CustomReplicationInfo.NextReplicationInfo;
                          UTPRI.CustomReplicationInfo.NextReplicationInfo = LPRI.NextReplicationInfo;
                          LPRI.Destroy();
                      }
                      else
                      {
                          LastLinked = UTPRI.CustomReplicationInfo;
                          do {
                              if (LastLinked.NextReplicationInfo != none && SeamLessTravelProblemLPRI(LastLinked.NextReplicationInfo) != none)
                              {
                                  LPRI = LastLinked.NextReplicationInfo;
                                  LastLinked.NextReplicationInfo = LPRI.NextReplicationInfo;
                                  LPRI.Destroy();
                              } else {
                                  LastLinked = LastLinked.NextReplicationInfo;
                              }
                          } until (LastLinked == none || LastLinked.NextReplicationInfo == none);
                      }
                  }
              }
          }
          
          function InitPRI(SeamLessTravelProblemLPRI PRI)
          {
              /* local MyStruct Setup; */
          
              if (PRI.bInitialSetupDone)
                  return;
          
              // set for replication after setup
              PRI.bInitialSetupDone = true;
          
              PRI.ServerReplicateSetup(/* Setup */);
          }
          
          function bool GetLPRI(UTPlayerReplicationInfo UTPRI, out SeamLessTravelProblemLPRI LPRI)
          {
              local UTLinkedReplicationInfo LastLinked;
              
              LastLinked = UTPRI.CustomReplicationInfo;
          
              // Get LPRI of linked list
              if (LastLinked != none)
              {
                  do
                  {
                      LPRI = SeamLessTravelProblemLPRI (LastLinked);
                      LastLinked = (LastLinked.NextReplicationInfo != none ? LastLinked.NextReplicationInfo : none);
                  } until (LPRI != none || LastLinked == none);
              }
          
              return (LPRI != none);
          }
          
          DefaultProperties
          {
          }

          Comment

          Working...
          X