Announcement

Collapse
No announcement yet.

UObjects pointed to by TSet elements are garbage collected

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

    UObjects pointed to by TSet elements are garbage collected

    Hello.

    I have a UActorComponent with a TSet member which has pointers to objects inheriting from UObject.
    Once in a while the GC collects the objects pointed to by the TSet elements.

    Unlike TArray the TSet cannot be specified as a 'UPROPERTY' so I was wondering how I can make the GC not collect the objects pointed to.

    I read this blog post: https://www.unrealengine.com/blog/ue...uld-know-about

    It states the following: "Caveat: Unlike TArray, TSets (and TMaps) are not directly supported as UPROPERTYs and so cannot be automatically replicated, serialized, etc. If a TSet (or TMap) is used with hard UObject references (example: TSet<UObject*>), it is up to the user to make sure those references are properly serialized for the purposes of garbage collection. Someone has to take out the trash…"

    So in regards to this I was wondering what is meant by the user having to properly serialize the references and if this is the solution to my problem?

    #2
    Hi,

    i had to reference objects in the owner UObject to stop them from being garbage collected. To do so, you have to implement AddReferencedObjects function in your class, for a little bit more information look at Marc's answers here.
    Alone: The Untold - a story driven horror game

    Comment


      #3
      +1 for an in depth tutorial/docs on garbage collection for non-UPROPERTY UObjects and customized serialization.

      I read the blog post too, all very good to have the warning but would be nice if there was some explanation somewhere of how to do this.

      Here's my guess at what you might do based on what I've seen in code/API reference - but I haven't actually tested this out!

      If you want to reference objects from within a non-UObject derived class (for example a struct with a TSet member):
      There is a class called FGCObject which has a virtual AddReferencedObjects(Collector) method. If you derive from this and override the method, you can call Collector.AddReferencedObject(PtrToMyUObject), which should prevent the UObject from being garbage collected as long as your class instance is still around. In fact now I look at it, there are actually overrides for passing a TSet or TMap of UObjects, so you don't need to bother iterating over the elements yourself.

      From within a UObject-derived class, as in your example:
      UObject also has an AddReferencedObjects method. It seems to be a bit bizarre, in that it's static rather than virtual, and takes a pointer to the object instance as a parameter. Still, it looks like it's basically for the same purpose, so you can override it and add your TSet in there. Remember to also call Super::AddReferencedObjects!

      One thing I don't really understand is why this isn't handled automatically (or at least optionally) by TSet and TMap themselves. I'm kind of hoping these will become usable as UPROPERTY types at some point, but perhaps the templated keying system makes this impossible. But even if that's the case, they could potentially take a template parameter specifying that the set/map class should itself derive from FGCObject and add it's own references.

      If you try this, let me know if it solves the issue.

      Comment


        #4
        Thanks for the answers. I have wanted to try them out but I ran into some problems.

        The first one is that I don't know how to override a static function (UObject::AddReferencedObjects) something I thought was not possible to do in the first place?

        I also tried out inheriting from FGCObject however it seems the compiler cannot find the object even though I added its header file "GCObject.h" and the intellisense itself gives me a description of the class when I hover mouse over it.
        I was also not under the impressions that structs could be inherited but then again there are some differences with the unreal engine compiler.

        A note is that the tooltip states that 'FGCObject' is a class although this seems a bit strange with the F prefix.
        I have tried inheriting from it both with a class and a struct and in each case I got: Can't find struct 'FGCObject' and Superclass GCObject of class 'classname' not found.

        Also I was wondering if making and implementation of a static function is not a bad idea in case you need other different implementations in the future to handle other cases where the GC should not collect objects?

        Comment


          #5
          You can indeed inherit from a struct, in C++ terms it is identical to a class, except that it defaults to public instead of private. UE4 will use the F prefix for classes as well as structs, if it does not fit into a more specific category such as Actor (A), UObject (U) and a couple of others. I'm not sure why you are having a problem deriving from it, it should work.

          Anyway, since you have a UObject derived class, you should probably be using the other approach. To override the method, just do as you would any other, except that you should omit the 'override' keyword, as that is reserved for virtual methods only. I imagine you probably also have to redeclare it as static too.

          Code:
          // MyObject.h
          UCLASS()
          class UMyObject: public UObject
          {
            GENERATED_BODY() // or GENERATED_UCLASS_BODY() if you're on an older version of the engine
          ...
          public:
            static void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector);
          ...
            TSet< USomeObject* > MySet;
          }
          
          // MyObject.cpp
          void UMyObject::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
          {
            UObject::AddReferencedObjects(InThis, Collector);
          
            auto This = CastChecked< UMyObject >(InThis);
            Collector.AddReferencedObjects(This->MySet);
          }
          Like I said, I haven't actually tested this myself, but I think it should work. Overriding a static method is indeed very unusual, I am guessing that the generated reflection code takes care of setting up a pointer to your override so that it behaves as if it were virtual.

          And for sure this is an annoying amount of effort to have to go to every time you want to embed a set or map of object references. I'd be interested in any input from Epic devs on this issue, and if it's likely to change any time soon.
          Last edited by kamrann; 02-15-2015, 02:27 PM.

          Comment


            #6
            I've implemented the static function as you mentioned. I assume that since it is static I'd have to make a cast on the 'UObject* InThis' to 'UMyObject' in order to get 'MySet'.
            The problem right now is that I need to call it but I do not know where to get a 'FReferenceCollector'

            Also in regards to the 'FGCObject' thing, the following code gives me "Superclass GCObject of class GCPreventer not found" and also "OtherCompilationError (2)":

            Code:
            // GCPreventer.h
            #include "GCObject.h"
            #include "GCPreventer.generated.h"
            
            UCLASS()
            class FGCPreventer : public FGCObject
            {
            	GENERATED_UCLASS_BODY()
            
            	void AddReferencedObjects(FReferenceCollector& Collector) override;
            };
            
            // GCPreventer.cpp
            #include "MyProjectName.h"
            #include "GCPreventer.h"
            
            FGCPreventer::FGCPreventer(const class FObjectInitializer& OI)
            	: Super(OI)
            {
            
            }
            
            void FGCPreventer::AddReferencedObjects(FReferenceCollector& Collector)
            {
            
            }
            Thanks for bearing with me on this.

            Comment


              #7
              No problem. Yep, sorry I made an error in the code. You're right, you need Cast< UMyObject >(InThis)->MySet.
              I believe that is all you need to do, and that the static method should be called automatically by the garbage collector through the base UObject. I could be wrong though. Did you try to run it and see if you still had a problem? Maybe put a breakpoint into your override and check that it gets executed.

              With the FGCObject, you can't have a UCLASS that doesn't derive from UObject, as you've done there. The intended use case for it is to use it as a base class for a USTRUCT (not sure about this), or anyway for a generic class/struct. So if you remove the generated header, UCLASS, GENERATED_UCLASS_BODY, and the UObject constructor, I expect it should compile fine. FGCObject should also not require you to do anything more, it registers objects of your class with the garbage collector in its constructor.

              Comment


                #8
                Ah of course that does make sense. I read somewhere, think it was the documentation, where it stated the GCUObject was in a non serialized context or something. I have just got so used to slam UCLAsS/USTRUCT in front of everything:P

                I have tried placing a break point in it but unfortunately it is never hit.

                IT seems it has to be called then. The same problem exists for the FGCObject as well.

                Comment


                  #9
                  I checked the engine code and it is clearly set up such that you need only override the methods, and the framework will call it automatically. I just added the static override to one of my UObject derived classes and the breakpoint was hit as the editor loaded up. I edit my code above to include the UCLASS macros and the required cast. Are you sure you have things setup and spelled in exactly the same way?

                  Comment


                    #10
                    I did indeed make a mistake. I had used 'UObject::AddReferencedObjects(InThis, Collector);' instead of 'UActorComponent::AddReferencedObjects(InThis, Collector);' since I'm extending the 'UActorComponent'.
                    This error was what caused the function to not get automatically called.

                    With it now called and adding the set I can wrong my code and it works now without crashing, however I still do not know how the FGCObject is supposed to work.
                    It seems I cannot create it using the 'FObjectInitializer' in the constructor of my actor component so the only other alternative I can see is to manually create it using smart pointers or 'new' keyword and 'delete' in an override of 'UActorComponent::BeginDestroy()'. Doing it this way however will not trigger the breakpoint which makes sense as the unreal code would have no way of actually knowing about the object and calling the function for me.

                    I am now also wondering what will happen to the 'TSet' now that the GC has been told not to touch the objects referenced from it and whether I need to dereference it at some point as well or if this is taken properly care of automatically when the owner object of the 'TSet' is garbage collected.
                    Last edited by Insanez; 02-15-2015, 11:58 PM.

                    Comment


                      #11
                      If someone finds the answer to this, writing a tutorial on the Wiki wouldn't go amiss to prevent people getting caught up by it in future

                      Comment


                        #12
                        Okay, so, it's good to know the UObject approach is working.

                        I'm not clear on how you're trying to use FGCObject. First off, you realise that by overriding UObject/AActor::GetReferencedObjects, you have no need for FGCObject in this instance? Assuming you're just trying to see how to use it...

                        Imagine you had the following struct
                        Code:
                        struct FMyStruct
                        {
                          TSet< UMyContainedObject* > MySet;
                        }
                        And embedded it in some other UObject derived class
                        Code:
                        UCLASS() 
                        class UMyOuterObject: public UObject // or AActor/whatever 
                        {
                          GENERATED_BODY()
                        
                        ... 
                          FMyStruct Str;
                        }
                        So your outer class indirectly contains a set of UObject pointers. You could protect these in the same way as above, by overriding AddReferencedObjects in UMyOuterObject, and adding Str.MySet to the collector. Problem is, you'd have to duplicate the code for every object that embedded an FMyStruct. You'd have further problems if you wanted a global FMyStruct, etc.

                        This is where you should use FGCObject.
                        Code:
                        struct FMyStruct: FGCObject 
                        {
                          TSet...
                        
                          virtual void AddReferencedObjects(FReferenceCollector& Collector) override
                          {
                            Collector.AddReferencedObjects(MySet);
                          } 
                        }
                        Note that in the case of FGCObject, you don't call the base class implementation of the method in your override, as it doesn't have one.

                        So now the referencing is encapsulated in your struct, so no matter how or where you create an FMyObject, the set members will always be referenced properly. The unreal code does know about your object automatically - if you look at FGCObject constructor and destructor, you can see it registers the instance with the garbage collection code. Also in the UObject case, so long as the outer object is referenced (as it will be unless you're doing something unusual) the set elements will be correctly cleaned up once the outer object ceases to exist.

                        Originally posted by TheJamsh View Post
                        If someone finds the answer to this, writing a tutorial on the Wiki wouldn't go amiss to prevent people getting caught up by it in future
                        You know, in my first post I was asking for such a tutorial, I might just end up being the one to write it, there's certainly a need. I'll do a bit of testing and if I'm confident enough that I understand how it works, I'll give it a go.

                        Comment


                          #13
                          FGCObject is used to make a non-UObject class compatible with the garbage collector. I'm not sure why it has even been brought up in this case since you're dealing with a UObject containing other UObjects.

                          Use Occam's razor. If TSet doesn't manage GC resources, what does? TArray.

                          If you absolutely think you need the hashing container functionality from TSet (hint: you probably don't*), you can use a FHashTable to wrap the array, or outright have a separate TSet mirror and keep the TArray only for refcount purposes.

                          -Camille

                          * The overhead from hashing is not negligible. Depending on the hash function used and the number of elements in your container, a hashset can actually end up more costly than using a simple array.
                          Last edited by cmartel; 02-16-2015, 07:13 PM.

                          Comment


                            #14
                            [MENTION=27203]kamrann[/MENTION]: did you write a tutorial for this? I'm entirely new to Unreal (but with knowledge in coding) and your explanation cleared things up for me clearly about how the GC works! Thank you. It would be nice to have a tutorial somewhere
                            [MENTION=20460]cmartel[/MENTION]: You have some nice tips there, but for my case I need TSet in order to gain the O(1) on find operation, and also the properties/functionalities of a Set (in mathematical term such as Union/Difference & uniqueness). My Sets would live troughout the entire game, so I guess in my context it is ok?.
                            Also wouldn't it be much easier to use kamrann's method and let the GC manage references instead of leaving the task to the programmer? I know as a C++ programmer one should be able to handle object creation and cleanups, but I know that i make mistakes and cause memory leaks from time to time...

                            Comment

                            Working...
                            X