Announcement

Collapse
No announcement yet.

Replicating TArrays crashes game

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

    Replicating TArrays crashes game

    I created an ActorComponent "MyActorComponent" with an Array of UObjects "UMyObject" that need to be replicated. I overrode the IsSupportedForNetworking and the GetLifetimeRepProperties on the UObject.

    (this is part psuedo code)

    class MyUObject : public UObject
    {


    virtual void IsSupportedForNetworking() override
    {
    return true;
    }

    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Replicated, Category="My Object"
    int32 bReplicatedUProperty;

    void GetLifetimeReplicationProps(outLifetimeProps) const
    {

    Super::GetLifetimeReplicationProps(outLifetimeProps);

    DOREPLIFETIME(MyUObject, bReplicatedUProperty);
    }
    }

    Then in MyActorComponent I do pretty much the samething; SetIsReplicated(true) and override GetLifetimeReplicationProps and override the ReplicateSubobjects function

    class MyActorComponent
    {


    MyActorComponent(SubobjectBuilderThinggy)
    {

    this->SetIsReplicated(true);
    }

    UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Replicated, Category="My Actor Component")
    TArray<UMyObject*> myObjects;

    void GetLifetimeReplicationProps(outLifetimeProps) const
    {

    Super::GetLifetimeReplicationProps(outLifetimeProps);

    DOREPLIFETIME(MyUObject, bReplicatedUProperty);
    }

    virtual bool ReplicateSubobjects(ActorChannel, Bunch, RepFlags) override
    {

    bool wroteSomething = Super::ReplicateSubobjects(ActorChannel, Bunch, RepFlags);

    for(int32 i = 0; i < myObjects.Num(); ++i)
    {

    wroteSomething |= ActorChannel->ReplicateSubobject(myObjects[i], *Bunch, *RepFlags);
    }
    return wroteSomething
    }

    }

    My Character Actor Creates the Component at BeginPlay and sets it to a property that is replicated and in the override of GetLifetimeReplicationProps is setup with DOREPLIFETIME. When I run the game the replication of the TArray crashes the game. When change the TArray to a single object and fix the corresponding objects, the game runs and replicates the value of the single object without problem.

    Honestly I would really just love an overview of how anyone else has done TArray<UObject> replication.
    Last edited by ArcainOne; 02-18-2015, 12:01 PM. Reason: Posted without finishing the question

    #2
    I'm 99.999% sure you aren't allowed to change what is and isn't replicated at Runtime. You have to do that at initialization, that's probably what's giving you the crash.

    So if you want something to replicate, you can't create bespoke replicated variables and properties like that or change whether something is replicated at runtime.

    Comment


      #3
      Okay, I'm not entirely sure I follow you. I am not sure how I am changing what is replicated at Runtime. If I use this exact same setup with a single UObject instead of an array of UObjects the replication happens without a problem. It is when I make the "myObjects" variable to a TArray<UMyObject*> instead f a normal UMyObject * (In the component).

      So ultimately my question is how do you properly replicate an array of UObjects?

      Comment


        #4
        It's a bit difficult to understand the psuedo-code, but if I've read what you said correctly (BTW, helps to write code in the [code] tags) - you're spawning an Actor Component on BeginPlay and then adding that to the list of replicated properties in the owning Actor right? If not ignore this next bit:

        Begin Play is called after the object has been created in the world and during runtime, at which point it's way too late to tell the game that the actor has to replicate something. It has to be done either as part of the constructor, or I suspect at latest during PostInitializeComponents() or PreInititalizeComponents(), never tried the latter though.

        As for Actor Components, they have a 'GetReplicatedLifetimeProps' of their own, you can use that to replicate variables of that actor component and they're considered for replicated like anything else, just make sure that you set the 'Replicates' value before it's created.
        Otherwise, if you have the Actor Component (which has the Array of UObjects) and the Actor set-up correctly, you should just be able to replicate the TArray like a normal array. The TArray has to be created before runtime though, meaning it has to be a UPROPERTY.

        If all that's done correctly, try using DOREPTARRAY in LifetimeReplicatedProps instead of DOREP_LIFETIME. This page on the documentation may help you... I'll be equally interested to know if replicating TArray's of UObjects is differrent to anything else:
        https://docs.unrealengine.com/latest...ays/index.html

        Comment


          #5
          Ah okay I see the confusion now. My owning actor has a UPROPERTY for the component setup before hand and that is set to be replicated from the get go. The property is initialized as NULL but is then created and set in Begin Play by the server. This seems to work fine as long as my component is not replicating a TArray within it. I setup a test in my ActorComponent where instead of an array I used a Single replicated UPROPERTY pointer that also was created on initialization of the component. This test was successful as the value was replicated to my client when it was changed on the server. This leads me to believe that as long as the property itself is set to be replicated then it can be null. My TArrays are always non pointers so having it exist before runtime is not an issue, it's contents however are a different matter haha.

          Okay I'll try the DOREPTARRAY but I noticed the documentation you gave it looks like it requires the override of GetReplicationList() is that what you ment to put down?. Also a rep function OnRep_[MyArray] but no example of that function in the documentation.

          My final confusion on this comes from the UAbilitySystemComponent that serializes and replicates a list of UAbility objects

          Documentation - UAbilitySystemComponent - here there is a property that is replicated called AllReplicatedInstancedAbilities which is a TArray of UGameplayAbility.

          In UAbilitySystemComponent::ReplicateSubobjects we see the list of AllReplicatedInstancedAbilities iterated through and added to the actor's replication channel.

          In the Documentation - UGameplayAbility I see no properties marked for replication or anything special about it... Just an object derrived from UObject.

          I have just started by grand foray into replication so pardon me if my supplemental questions seem elementary Thanks.

          Comment


            #6
            Okay so I finally successfully replicated a TArray of Objects, it is fairly simple once you get your head around Replication.
            Begin with defining the UObject you want replicated. I actually create a UNetworkObject as a base class for all UObjects I want replicated. But the quick and dirty way is to simply override the IsSupportedForNetworking Then mark the properties you need replicated as UPROPERTY(Replicated)

            Code:
            class UNetworkObject : UObject
            {
                virtual bool IsSupportedForNetworking() override;
            
                virtual bool ReplicateSubobjects(AActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags); // note no override because this is the FIRST declaration of this function.
            }
            With your Replicatable UObject setup you can now replicate the property from a parent Component or Actor... But What if your parent object IS another UObject? You will need to provide an implementation of ReplicateSubobjects

            Code:
            bool UNetworkObject::ReplicateSubobjects(AActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags)
            {
                return false;
            }
            Now this base object can replicate it's own subobjects if you derive new objects from it. A bit of confusion arose for me because I did not have to declare GetLifetimeReplicatedProps, but a compile time error will happen if you don't define that function when you have a uproperty marked replicated.

            The following example demonstrates how to Replicate a TArray of subobjects from within another UObject using an ActorComponent (whch uses an AActor object) for replication.

            MyTestObject.h
            My Test object is a simple UObject that needs to replicate some information, but is complex enough that it does not warrent a simple Struct to do the job. For demonstration purposes I chose a very simple object.
            Code:
            class UMyTestObject : UNetworkObject
            {
                UPROPERTY(Replicated)
                float MyTestProperty;
            }
            MyTestObject.cpp
            Code:
            
            void UMyTestObject::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
            {
                DOREPLIFETIME(MyTestObject, MyTestProperty);
            }
            Now my Second UObject that is the outer to "MyTestObject.

            MyTestObjectManager.h
            The TestObjectManager simply contains a bunch of MyTestObjects. Ideally this class would also provide some accessor functionality but for demonstration purposes I have left that out.
            Code:
            class UMyTestObjectManager : UNetworkObject
            {
                UPROPERTY(Replicated)
                TArray<UMyTestObject*> MyTestObjects;
            
                void BuildArrayOfTestObjects(int32 numObjects); // I'm not going to define this for demonstration sake, it would just create a bunch of UMyTestObjects and add them to the array.
            
                virtual bool ReplicateSubobjects(AActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags) override; // override from the base class
            }
            MyTestObjectManager.cpp
            Code:
            
            void UMyTestObjectManager::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
            {
                DOREPLIFETIME(UMyTestObjectManager, MyTestObjects);
            }
            
            bool UMyTestObjectManager::ReplicateSubobjects(AActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags)
            {
                bool wroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);
            
                for(UMyTestObject * myObject : MyTestObjects)
                {
                    if(myObject)
                   {
                       Channel->ReplicateObject(myObject, *Bunch, *RepFlags)
                   }
                }
            }
            Okay now I don't know exactly the details of what this is doing on the back end but think I've pretty much figured out why it works. First MyTestObject has a variable that freely replicates because it is a simple variable type (bool, int, float, struct). MyTestObjectManager contains an array of MyTestObjects. Now the TArray is marked to be replicated and TArrays support replication . The replication of THAT array occurs, but it's contents do not because they are not simple variable types but instead pointers to UObjects. That is where ReplicateSubobjects comes into play. It iterates over each object in the array and replicates it to the client.

            "But you said UObjects don't replicate, your MyTestObjectManager is a UObject, How is it replicating?" - Well my friend we are not done.

            The actual replication for this REQUIRES either an Actor OR a ActorComponent. Either way the procedure is the same.

            MyActorComponent.h
            MyActorComponent simply demonstrates how to do replication with a Component. You can just as easily use an AActor.
            Code:
            class UMyActorComponent : UActorComponent
            {
                UMyActorComponent();
            
                virtual void InitializeComponent() override;
            
                UMyTestObjectManager * myTestObjectManager;
            
                virtual bool ReplicateSubobjects(AActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags) override;
            }
            MyActorComponent.cpp
            Code:
                UMyActorComponent::UMyActorComponent()
                {
                    this->bWantsInitializeComponent = true;
                    this->SetIsReplicated(true);
                }
            
                void UMyActorComponent::InitializeComponent()
                {
                    this->myTestObjectManager = ConstructObject<UMyTestObjectManager>(UMyTestObjectManager::StaticClass(), this);
                    if(this->myTestObjectManager)
                    {
                         this->myTestObjectManager->BuildArrayOfTestObjects(15);
                    }
                }
            
                void UMyActorComponent::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
                {
                    DOREPLIFETIME(UMyActorComponent, myTestObjectManager);
                }
            
                bool UMyActorComponent::ReplicateSubobjects(AActorChannel * Channel, FOutBunch * Bunch, FReplicationFlags * RepFlags)
                {
                    bool wroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);
                    wroteSomething |= Channel->ReplicateSubobject(myTestObjectManager, *Bunch, *RepFlags); // replicate the subobject
                    wroteSomething |= myTestObjectManager->ReplicateSubobjects(Channel, Bunch, RepFlags); // replicate the subobject's subobjects.
            
                    return wroteSomething;
                }
            Now one thing to note. Remote Procedure Calls on a UObject (Client or Server functions) cannot be performed (at least to my knowledge and without some major coding upgrades made by you). I have heard it theoretically possible... but the technical process currently is beyond where I want (or need) to go. So if you want to do RPC functions you must do them on an Actor or a UActorComponent. For instance lets say you want to set the value:

            Code:
            UMyActorComponent->myTestObjectManager->myTestObjects[0]->MyTestProperty = 25.22f
            The cool thing is this will actually replicate up. But what if all these properties are not public and you ONLY want the setting of this to be performed on the Server? You'd have to setup a daisy chain starting from the UMyActorComponent.

            Code:
            void UMyActorComponent::SetValueAtObjectIndex(int32 objectIndex, float value)
            {
                if(this->Role == RP_Authority)
                {
                    this->myTestObjectManager->SetValueAtIndex(objectIndex, value);
                }
                else
                {
                    this->ServerSetValueAtObjectIndex(objectIndex, value);
                }
            }
            
            bool UMyActorComponent::ServerSetValueAtObjectIndex_Validate(int32 objectIndex, float value)
            {
                // do some logic to validate the user is not cheating
                return true; // return false if you want to automatically kick the cheater from your game.
            }
            
            void UMyActorComponent::ServerSetValueAtObjectIndex_Implementation(int32 objectIndex, float value)
            {
                this->SetValueAtObjectIndex(objectIndex, value) // no point in duplicating code, this should be the authority now.
            }
            This will effectively change the value on the server, and then replicate the MyTestObject down to the client.
            Last edited by ArcainOne; 02-27-2015, 07:44 PM. Reason: Clerifying the example

            Comment

            Working...
            X