Announcement

Collapse
No announcement yet.

Deperately need some replication help

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

    Deperately need some replication help

    Hey all,
    I'm working on an Unreal TC Mod and lets just say we decided to start network playtesting a little late, and we're running into network replication problems.

    So I'm making a racing mod for Unreal, and for this we needed to make it possible to have 8 teams. To do this, I extended the GameInfo and GameReplicationInfo classes. In my game replication info class I have:

    Code:
    class RacingGameReplicationInfo extends GameReplicationInfo;
    
    var xRacingTeamInfo RacingTeams[8];
    
    replication
    {
       reliable if (Role == ROLE_Authority)
          RacingTeams, addToTeam;
    }
    
    simulated function bool addToTeam(Controller Other, int newTeam)
    {
        // Add Other to Team number newTeam
        // Return True if added successfully
    }
    and in my PlayerControllerClass I have

    Code:
    class xRacingPlayer extends xPlayer config(RacingUser);
    
    exec function setTeam(int newTeam)
    {
         if (RacingGameReplicationInfo(GameReplicationInfo).AddToTeam(self,newTeam))
            clientMessage("Successfully added to team"@newTeam);
         else
            clientMessage("Unsuccessful adding to team"@newTeam);
    }
    The way this is meant to work is the user goes to the terminal and types "Setteam Teamnumber" and they are placed on that team. So this works perfectly in Instant Action and on the server, however, on any network clients, the player simply gets the message "Unsuccessful adding".

    Some simply debugging showed me that the AddToTeam function is never even getting called on the clients.

    Not only that but if I cannot read teams from the RacingTeams variable on clients. For example if I go to the server and type Setteam 0, then tell it to print out all the players on team 0, I get a correct listing of players on team 0 (only the server player). However, if I join a team on the server and tell the client to print out a list of racers on team 0, i get nothing.

    I print out the racers on a team by inserting
    Code:
    clientMessage(""@RacingGameReplicationInfo(GameReplicationInfo).RacingTeams[0]);
    into the exec setteam function.

    Why can't I call addToTeam on clients, and why aren't teams I set on the server replicated to the clients?

    Help please
    Thanks!

    #2
    Without fully understanding what you are talking about, I'm going to guess that your replication scheme is wrong. Replication is double coding.

    You are running an exec function (which is clientside), and that calls a replicated function that is only supposed to replicate from server to client. What you need to do is this (for example):

    Call an exec, calls a serverside function (Role < ROLE_Authority), which changes the value. That function calls a clientside function on all clients (Role == ROLE_Authority) to update the array with the new info, this assures that the server sets the real value on the clients. Then you run another clientside function (exec) to display the values. This is just for testing purposes, but the idea is there.

    Comment


      #3
      Try this:

      //-----------------------------------------------------------------------------
      class RacingGameReplicationInfo extends GameReplicationInfo;

      var xRacingTeamInfo RacingTeams[8];

      replication
      {
      reliable if (Role == ROLE_Authority)
      RacingTeams;
      }

      function bool addToTeam(Controller Other, int newTeam)
      {
      // Add Other to Team number newTeam
      // Return True if added successfully
      }

      //-----------------------------------------------------------------------------


      class xRacingPlayer extends xPlayer config(RacingUser);

      replication
      {
      reliable if (Role < ROLE_Authority)
      setTeam;
      }

      simulated exec function setTeam(int newTeam)
      {
      if (RacingGameReplicationInfo(GameReplicationInfo).Ad dToTeam(self,newTeam))
      clientMessage("Successfully added to team"@newTeam);
      else
      clientMessage("Unsuccessful adding to team"@newTeam);
      }

      //-----------------------------------------------------------------------------

      Comment


        #4
        I believe the correct way to think about Replication is this:

        The server runs the a *complete*game independently of any clients, except it *allows* human players over the network to be controllers of "pawns" in this complete game.

        By "complete game" I mean that mathematically, the game state can be seen in its entirety at any moment in time *just* by looking at the server itself. The server has all variables set such that the game state could in theory be serialized.

        The clients must execute code, not just the server, for client I/O reasons:
        1) effects (output)
        2) user input (input)

        The game state on the client is far from complete. It is a very very small subset of information that the server gives to the client (press F6 and see "Channels" -- channels are the streams of information that direct the client on how to reconstruct the subset of the game state). Here is how the server/client sync up:

        - simulated functions: these are like a muliprocesor version of fork() in Unix. They split a function into two "processes" so to speak, one process running on the server CPU, the other running on the remote client CPU. The Actor variable "Role" together with "if" statements is for keeping straight which CPU should run which code. Typically, simulated functions are used for making the same thing happen on the server and the client, but with slightly different levels of detail (the client being graphically detailed, the server being game-state detailed).
        - replication: the client can not be trusted to correctly calculate the effect over longer periods of time, so instead of just running code on the client, we brute force update the variables periodically using replication.

        The only things that need to be replicated are things that the client can not be trusted to keep a good version of continuously. Most simple effects are short and can be spawned solely on the client (just for looks). Those variables do not need to be replicated.
        You want to replicate *only* those things that are needed for I/O on the client generally. DO NOT try to simulate game state code itself! Just do effects that render subsets of the game state to the client.

        Hopefully a reasonable understanding of the motivation behind replication will cause things to start making more sense.

        Having said all that, I now ask, how do exec functions work over the network?? I have never delved into that stuff!

        I also do not understand when to use "reliable if" and when to use "unreliable if". What is the difference between
        reliable if(ROLE == ROLE_Authority)
        and
        unreliable if(ROLE < ROLE_Authority)
        ??

        Comment


          #5
          Exec functions only work on the client, so an exec function to spit out the current variables is good for checking to see what got replicated from the server (in a hack based way you set the server, as you would in the normal game, and then see if it replicated that change back to you successfully).

          Reliable functions are for things that MUST ABSOLUTELY ALWAYS be replicated, such as ammo, firing, yadda yadda. Things that ABSOLUTELY MUST OCCOUR OR THE UNIVERSE MIGHT REBOOT! :bulb:

          Unreliable are for things that are nice to have when they will fit down the pipe without clogging it. Player movements (at least in UT99) are unreliable because there is just SO MANY of them, if you miss one or two now and then you'll just catch up later on. This is why the player sometimes "warps" when things get busy. Unreliable is much faster, since it can be discarded if full.

          Also important to note that replication takes time. Sure, we all know that, but when you make a mutator that replicates a TON of information, such as mapvote, you run the risk of clogging the pipe with all the maps, player names, vote counts, game types, etc. that get sent to everyone. It all arrives there eventually, but when you send a ton of info, you need to verify that it got there before you go ahead with your code.

          Replicated functions dont wait to recieve return values, so thats a pretty useless way of coding it. Infact, I dont think I've ever seen a replicated function return anything.

          Comment


            #6
            So basically then

            reliable if()
            and
            unreliable if()

            are identical with respect to both *causing* replication to occur on the variables they govern,

            both different with respect to the garantee they put on the frequency with which these variables are replicated
            ??

            reliable if() transmits the new values every ______________.

            and unreliable if() transmits the new values every ______________ but only when there is sufficient bandwidth, where "sufficient" is defined as __________________.

            Could you please fill in those blanks?

            Comment


              #7
              Replication happens when you do it. Every time, for instance, you fire your weapon, things get replicated. Not in between.

              Other things, like movements, happen about every frame (thats why net clients are limited to so many fps). Things like score are replicated when someone dies.

              As for the unreliable, that depends on your netspeed I believe. It might also depend on native variables I dont know about. Each person, and each server, can replicate different amounts of data, depending on connection, PL, ping, etc. so there isnt really a static number.

              Also there are settings which control replication. For instance. bNetOwner only replicates to the person who owns the given actor. bNetInitial only replicates when the actor is created. bNetDirty is a new one to UT200x that replicates only when the data has changed. Using these control variables, you can optimize replication (this is UT200x's "improved" netcode).

              Comment


                #8
                I recommend reading the latest version of the ReplicationDeObfuscation document that is floating around the net. It can definately help you to understand network support. Heck, I have been programming for Unreal for years now and I still get confused every once and awhile.

                With that said I will try to clear up a couple of things here:

                The reliable and unreliable statements set up conditions for when something is to be replicated. These are used to transfer both variables and function calls to the other end.

                IMPORTANT: Even if a variable is listed under an unreliable statement - it will be RELIABLY replicated.

                ANOTHER IMPORTANT THING: Replication occurs at the end of each tick cycle - thus you could do something like this:

                function DoSomething()
                {
                f++;
                f--;
                }

                where f was a replicated variable. At first glance you would think that f would be replicated twice - but that is not so. Since the value at the end of the tick is the same as it was at the beginning of the tick, no replication occurs.


                Variables are updated according to its NetPriority and NetUpdateFrequency settings.

                The NetUpdateFrequency sets how often to attempt to update the variables.

                The NetPriority sets the relative importantance of updating the variables for that particular class.

                Variables are replicated in order of priority. Each tick the classes for which enough time has passed by (their NetUpdateFrequency) to warrant replication to occur will be added to a list of all variables to be updated. These are then replicated in order of priority (classwise not per variable and is set by NetPriority) If insufficient bandwidth is available, the replication is delayed until next tick. (If I recall right)


                If reliable is set for a function call, it will be reliably called - every time it is called. If it is set as unreliable then it will only be called if there is bandwidth available at that particular instance. If no bandwidth is available the function call transfer is skipped.

                Comment


                  #9
                  THank you very much for that info!

                  I still have two questions:

                  1) How does specifying a function as reliable/unreliable work together with the "simulated" keyword?
                  2) Is bNetDirty or a setting? or a flag? Does it set up replication for an actor throughout a game? Or does it change during runtime to indicate the need for replication? In other words, is it like a garbage collection flag that says "delete me now" except that instead of deleting the actor, the engine replicates it?

                  Comment


                    #10
                    Simulated only needs to be declared on functions that are inteded to run on the CLIENT. The server runs them as well, but they are slightly slower than regular functions, so dont just go and declare everything simulated if it doesnt have to be. Functions must be simulated to be called on the client by the server. Most other situations, simulated is not required.

                    Comment


                      #11
                      Yah i know that, but how does that work with the reliable/unreliable keyword?

                      I would assume that *all* simulated functions are replicated, and that simulated functions are reliable by default unless specified as unreliable. Is this correct?

                      Comment


                        #12
                        No.

                        Simulated means absolutely nothing on its own. You have to specify ALL replicated functions in the repliction block. You specify them as reliable/unreliable based on your requirements, and their use. Then, if a function is supposed to be called from the server on the client, you must specify that it is simulated, or it will not run on the client (but still runs on the server).

                        Comment


                          #13
                          You are correct in that bNetDirty acts like a flag to help notify when replication is needed.


                          Another thing about Simulated functions and replication to keep in mind:

                          Simulated functions can only be called from other simulated functions and still be executed on the client. Thus if you are in a regular function and make a call to a simulated function it will not work (on the client that is - it always works on the server) UNLESS that particular function is also specified in the replication block.

                          Simulated function calls CAN be initiated by event calls where the event is also simulated. Examples of event calls are:

                          PreBeginPlay()

                          PostBeginPlay()

                          Tick()

                          and

                          Timer()

                          The whole list of events can be found in the Actor class.

                          Comment


                            #14
                            Simulated functions can only be called from other simulated functions and still be executed on the client. Thus if you are in a regular function and make a call to a simulated function it will not work (on the client that is - it always works on the server) UNLESS that particular function is also specified in the replication block.
                            This is unclear.
                            It seems like it would be rather useless if regular functions could not called simulated functions. I could swear I have seen lots of code doing that. If it were not possible, then only simulated events would be able to call simulated functions to get the "chain reaction" of simulated calls going.

                            BUT that last bit starting at "UNLESS" is confusing. Are you saying that functions (even regular functions) can be specified as replicated and NOT themselves be "simulated", but call other simulated functions?

                            In other words, is what you meant to say this:
                            - Replicated functions (simulated or not) can call simulated functions.
                            - Simulated functions can call simulated functions.

                            ???

                            And, are simulated functions practically always replicated?? or are there uses for simulated functions that are not replicated somehow? What exactly does "function replication" entail, if not simulation itself. Is the client just signalled when a replicated but non-simulated function has been called on the server (but not actually executed on the client)? Does it merely produce a call-stack slot on the client (including the arguments and return value as calculated by the server), so that further simulated functions can be called on the client? Otherwise, why would you replicate a non-simulated function?

                            Comment


                              #15
                              You have to keep in mind that the server and client typically run a seperate execution stream - how seperate depends on the RemoteRole setting.

                              For RemoteRole=ROLE_None only the side on which that particular class is created (the Role=ROLE_Authority side) will run an execution stream. There is no Remote execution stream operating here.
                              99% of the time this would be a server only operation. Every once and awhile you get a client only operation. There is no network overhead in this case (obviously).

                              For RemoteRole=ROLE_AutonomousProxy both the Client and Server will run an execution stream. They will run completely seperate of each other and again no network overhead in this case. I think that both sides become ROLE_Authority in this case if I recall correctly once the class is initiated...

                              For RemoteRole=ROLE_SimulatedProxy both the Client and Server will run an execution stream. This is where the replication settings and simulation comes into effect.

                              A regular function on the Role=ROLE_Authority side can call both simulated and regular functions at will and they will be executed. Normally this will be on the Server side. All functions that get called on the Role=ROLE_Authority side will be executed regardless of the simulated setting. If a function is called that is in the replication block then that function will be called on the Remote side.

                              The execution stream on the RemoteRole=ROLE_SimulatedProxy side is operating concurrently with that of the Role=ROLE_AutonomousProxy side. Both sides basically run seperately from each other EXCEPT when they replicate a call. Simulated function calls are not called from the other side UNLESS they are a replicated function. Most of the time the simulated function calls are initiated by either a simulated exec call or a simulated event call on the RemoteRole=ROLE_SimulatedProxy side. Alternatively you can initiate with a simulated replicated function call from the server (but remember there is a delay in this method) Thus on the Remote side the execution stream must be initiated by a simulated function or the stream will not run at all...

                              For RemoteRole=ROLE_DumbProxy then there is no simulation on the Remote side occuring if I recall correctly...

                              Recall that on clients most of the time you just want to be doing stuff that has to do with FX and display things. Thus typically only functions dealing with this kinda stuff are made simulated functions. Thus event calls can be very useful to initiate these calls since they typically occur at particular times (usually following a special event).

                              A good example of a non-simulated replicated function would be a call from a simulated exec function that you have linked to a keybind. What you really want is for all functionality behind that keypress to be executed on the server but it gets called on the client first (since that is where you are pressing a button to initiate it) and the client must replicate the call but keep any functionality from occuring on the client. Thus you make the function replicated but not simulated.

                              Comment

                              Working...
                              X