Announcement

Collapse
No announcement yet.

Blueprint pitfalls and death traps

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

    Blueprint pitfalls and death traps

    Warning: long post ahead! My apologies in advance...

    After quite a few months of working on a fairly big BP-based project, I've come to notice a few blueprinting patterns that really should be avoided, because they can potentially cause anything from minor annoyances to critical, project-threatening problems.

    I thought it might be a good idea to share my findings here, so others can avoid these particular issues.

    If you have bumped into something BP-related that simply turned out to be a really, really bad way of doing things for whatever reason, then by all means add your experiences to this thread!



    UPDATE (October 24, 2014): THE INFORMATION BELOW RELATES TO VERSION 4.3 (that's old by now)
    Epic has made lots of changes related to linking and circular dependency checks et cetera since then, so these caveats may not apply in the current version (i.e. 4.5.1 as I'm writing this addendum). Further studies are needed! Test things yourself. If you have results that contradict or confirm the notes below, please post about it.



    ~

    Pitfall: Custom BP-class arguments to BP interface functions

    The scenario is this: You've got some Blueprint class, lets call it ThePawn_BP, and it's a custom class based on the built-in pawn.h class. Then you have created an interface, TheInterface_BP, which declares a bunch of functions. One of these functions is called DoSomething, and it expects a ThePawn_BP object as one of its input arguments.

    All fine so far. Now you implement this interface in one of your other classes, ItsATrap_BP, and you set up the DoSomething functionality there. It gets ThePawn_BP input object, and as the name suggests it does something with the pawn-derived object, and everything is lovely. You playtest and ItsATrap_BP:oSomething actually does exactly what it's supposed to do, whatever that is. Great.

    You save your project, quit the editor and take a break for lunch.

    Around 13:05 you crawl back to your workstation and open the project again. You press play and soon notice that ItsATrap_BP:oSomething has stopped working, or at least it no longer does anything at all. You suspect someone renamed it to DoNothingAtAll, and start making plans to "interview" your neighbours about it, but first you open up ItsATrap_BP to have a look at the DoSomething implementation. It turns out that the wire between the input node's ThePawn_BP object has disappeared, so it's no longer connected to the rest of your nodes in that function. You reconnect it, playtest, and it works again.

    The problem is that the wire in question will get disconnected every single time you save, exit and reload this project. I'm not saying that this is guaranteed to happen, but it's fairly likely.

    SUMMARY: Custom BP object arguments to BP interface function implementations are unreliable.
    SOLUTION: Always use built-in classes as arguments to interface functions. Do not use custom BP arguments. In the example above, the solution is to make DoSomething take a Pawn or Actor argument instead, and then you cast to ThePawn_BP inside the function implementation before doing whatever it is you're doing.

    ~

    Death trap: Casting to custom BP-objects in MacroLibrary macros

    The pitfall above is a "friendly" kind of problem, because it doesn't disable your project in general, it just silently fails to do something, repeatedly, until you fix it, so it's not a big deal. The death trap I'm about to describe now however, is quite a bit more nasty, because it can cause all sorts of erratic in-editor crashes or even prevent you from opening the project at all, forcing a roll-back to a previous revision and very likely a pending overconsumption of migraine medication.

    Here's the scenario: You have a custom player controller class, ThePC_BP, based on PlayerController.h as per usual. In this class you have a function called MakeStuffHappen, which makes stuff happen. It works fine.

    In your level there's a small zoo of different custom blueprint actors that sometimes need to call ThePC_BP::MakeStuffHappen for whatever reason. These may be stand-alone blueprints derived directly from Actor or Pawn, or maybe "second generation" child-blueprints derived from some custom base class that you have created.

    At any rate, to call ThePC_BP::MakeStuffHappen, they need to call GetPlayerController, then cast the reference to ThePC_BP, and then they can call the function in question.

    After a while you notice that this little chain of nodes shows up in lots of places. To clean things up, you decide to create a Blueprint MacroLibrary, in which you create a macro called GetThePC. It simply performs that little sequence of calls and returns a properly cast ThePC_BP link along with two execution pins, one for Success and one for Failed (in case the cast didn't work for some reason).

    You start placing this macro everywhere in your Blueprints where you need a link to ThePC_BP, and it seems to work fine, and your graphs are much cleaner and easier to read. Awesome.

    Now here's the kicker: at some point, maybe today, maybe next week, maybe when you place the thirteenth instance of that macro somewhere, maybe when you create a new child BP based on a BP that uses the macro - your project may suddenly fail to open. It will just crash on launch. Or, if you are lucky, nothing bad happens at all. If it goes south, a roll-back to a previous revision seems to be the only way to get back on track.

    I have tried to create a minimal reproduction case for this particular problem, but so far it's just completely random.

    SUMMARY: Using a macro in a MacroLibrary to cast something to a custom BP class is potentially catastrophic.
    SOLUTION: Just don't do it. Come up with another way to do whatever it is that you are doing.

    ~

    Random thoughts

    The common factor in both these problems seems to be related to the use of custom BP classes/types as opposed to built-in classes and Plain Old Data. Similar issues seem to arise when using BP FunctionLibraries, in that such functions appear to work fully reliably only when they are set up to perform operations on built-ins or PODs, while any use of custom classes (playercontroller, HUD, ...) can cause various nasty effects.

    Without knowing the exact procedures involved when blueprints are loaded, evaluated and compiled, I can only guess that the core issue boils down to circular or out-of-order dependencies. A macro tries to use a BP class it doesn't yet know about, or a BP has some calls evaluated while the call target doesn't yet exist...

    It's probably very easy to create these situations inadvertently while building blueprints on the fly, so to speak, without any well thought-out plan for the overall architecture. The nasty thing is when the problem doesn't show up until the next time you reload the project.

    When writing C++, we often make sure that, if necessary, class A knows about class B in advance, by employing things like forward declaration or just enforcing overall sanity simply by the order in which different classes and functions appear in the code file. In Blueprinting, there are no such mechanisms in place (as far as I know at least) and we mostly have no idea about in which particular order various classes are initialized when the project builds/loads. This can sometimes make it difficult to predict when a particular BP pattern is treading dangerously close to the abyss.

    Anyway, some of these issues have appeared as reports on the AnswerHub, in one form or the other. I think the interface-related issue is sort of known, but I'm not entirely sure. The project-killing issue is less clear. I've seen a few reports that may or may not be related to the example I've given here, and the problem is further complicated by the fact that it's extremely difficult to create a minimal case that reliably reproduces it. And without that, it's very hard for the developers to fix.

    And finally, if you had asked me about the project-killer scenario a week ago, I would have said it was a myth. It is so weird, you'll just assume it's some stupid user error that would obviously never ever happen to you... It is, however, more diabolical than that.
    Last edited by Xenome; 10-24-2014, 01:57 AM.

    #2
    Originally posted by Xenome View Post
    Death trap: Casting to custom BP-objects in MacroLibrary macros

    The pitfall above is a "friendly" kind of problem, because it doesn't disable your project in general, it just silently fails to do something, repeatedly, until you fix it, so it's not a big deal. The death trap I'm about to describe now however, is quite a bit more nasty, because it can cause all sorts of erratic in-editor crashes or even prevent you from opening the project at all, forcing a roll-back to a previous revision and very likely a pending overconsumption of migraine medication.

    Here's the scenario: You have a custom player controller class, ThePC_BP, based on PlayerController.h as per usual. In this class you have a function called MakeStuffHappen, which makes stuff happen. It works fine.

    In your level there's a small zoo of different custom blueprint actors that sometimes need to call ThePC_BP::MakeStuffHappen for whatever reason. These may be stand-alone blueprints derived directly from Actor or Pawn, or maybe "second generation" child-blueprints derived from some custom base class that you have created.

    At any rate, to call ThePC_BP::MakeStuffHappen, they need to call GetPlayerController, then cast the reference to ThePC_BP, and then they can call the function in question.

    After a while you notice that this little chain of nodes shows up in lots of places. To clean things up, you decide to create a Blueprint MacroLibrary, in which you create a macro called GetThePC. It simply performs that little sequence of calls and returns a properly cast ThePC_BP link along with two execution pins, one for Success and one for Failed (in case the cast didn't work for some reason).

    You start placing this macro everywhere in your Blueprints where you need a link to ThePC_BP, and it seems to work fine, and your graphs are much cleaner and easier to read. Awesome.

    Now here's the kicker: at some point, maybe today, maybe next week, maybe when you place the thirteenth instance of that macro somewhere, maybe when you create a new child BP based on a BP that uses the macro - your project may suddenly fail to open. It will just crash on launch. Or, if you are lucky, nothing bad happens at all. If it goes south, a roll-back to a previous revision seems to be the only way to get back on track.

    I have tried to create a minimal reproduction case for this particular problem, but so far it's just completely random.

    SUMMARY: Using a macro in a MacroLibrary to cast something to a custom BP class is potentially catastrophic.
    SOLUTION: Just don't do it. Come up with another way to do whatever it is that you are doing.
    Exactly...Although my issue is with Function Library not Macro. My project fails to open.

    https://answers.unrealengine.com/que...ed-childc.html
    https://answers.unrealengine.com/que...n-library.html
    Unreal Issue Tracker - Desktop App for Unreal Engine Issues
    Unreal Nexus - Create Share and Experience!

    Unreal Engine 4 World-Wide User Map

    [PLUGIN] Aws Gamelift Client Plugin
    [TOOL] Create your own binary release of UE4 from GitHub source
    [Marketplace] Vehicle Soccer Template
    [Marketplace] Objective Waypoint System
    My UE4 Tutorials and Misc Tools

    Lead Programmer at YetiTech Studios
    Your Friendly Neighborhood
    Satheesh PV (a.k.a RyanJon2040) | Twitter, Instagram, Facebook, LinkedIn, YouTube, Google+

    Comment


      #3
      Avoid using BP structs and enums if you ever wish to access them from C++ in the future

      Blueprint structs and enums sounds like fun to use since you can modify them at will without having to quit the editor to recompile, but if you ever need to use them from C++ later you're screwed. Currently there is no way to replace the BP struct/enum by a C++ one: if you create one using the exact same name, both BP and C++ versions will show in the type browser and if you try to delete the struct/enum asset the C++ version can't be picked to replace the references (you can only replace assets by other assets). The only way is to go through all blueprints that use that struct/enum and modify them by hand.

      SUMMARY: You cannot replace references to Blueprint structs/enums by C++ structs/enums
      SOLUTION: Unless you're working on a content-only project, create your structs and enums in C++. It might be more laborious, but it's surely better than having to replace all references by hand on dozens of blueprints later on when you need to create a C++ function that takes in said struct/enum.
      Last edited by pedro_clericuzzi; 08-13-2014, 10:10 AM.

      Comment


        #4
        Thanks for this thread - the first gotcha in the list is one I ran into, and I imagine plenty of others will as well.
        Storyteller - An immersive VR audiobook player

        Dungeon Survival - WIP First person dungeon crawler with a focus on survival and environmental gameplay ala roguelikes

        Comment


          #5
          Off the cuff, both issues you call out sound like circular dependency issues. For example: BP_A uses BP_B, BP_B uses BP_C, BP_C uses BP_A. When you go to open BP_A it needs to load all its dependencies before it can fully compile the Blueprint. So it goes off to load BP_B, and BP_B needs C, which needs A. Wait, aren't we already loading A? As you can imagine these scenarios can get quite hairy and untangling them can be tough.

          We've been playing whack-a-mole with issues like this over the past couple months, and we've been fixing them ad-hock as they arise. However, recently, these issues have been cropping up a lot more, which means: 1) Users are pushing the system to its limits, and 2) We've finally hit a wall where fixing one issue alters the load order and uncovers a different issue that we already fixed.

          Just last week, we sat down and hashed out a plan to make a sweeping fix for all of these types of issues. This plan involves refactoring the Blueprint loading code and will take a bit to get right, but now we have a plan of attack!

          In the meantime, unfortunately, the best advice I can give is to be mindful of your dependency chain. Adding a new dependency could complete the circle and be the straw that broke the camel's back (even if it is 3+ assets removed).

          That said, we have fixed up a lot of circular dependency issues, so don't be afraid of using it when needed. If you do come across a scenario that isn't supported (like the one's you've described here), then please try to identify the dependency chain (like: "the BP_X implements an interface, that has a BP_Y parameter, and BP_Y calls a function on BP_X"), and let us know (either here or on answerhub). Having all these scenarios on hand will help us make sure that the refactor correctly addresses everything (and we could maybe help you with workarounds in the meantime).

          Hope this is a satisfactory explanation about what is going on!
          Last edited by User-1420270633; 08-13-2014, 05:16 PM. Reason: Grammar/spelling mistakes

          Comment


            #6
            Originally posted by Mike.Beach View Post
            Off the cuff, both issues you call out sound like circular dependency issues.
            Yes, that was (one of) my conclusion(s) as well. They can be hard to spot when you are in the middle of a blueprinting frenzy.

            Great to hear that you are working on it! Your explanation is definitely satisfactory.

            Comment


              #7
              I hate you. I read this last night in bed on my iPad after shutting down UE4 for the night and though, huh, I do that, but have not issues. Today, when I launched my project, it crashed. Thanks for that. J/K, glad I saw this or I'd have no idea where to start.

              Now, my issue is that I moved a bunch of assets and when I roll back I have 2 of each in different places. Seems like some work some don't and I have no idea how to fix that. This is going to be a long day...
              Gooner44
              Cribbage Sample Project on MP

              Comment


                #8
                So, at least for me so far, the fix was easy. Rather than roll back since that caused other issues for some reason, I just deleted my macro library. Doing that allowed me to open the project, get a ton of errors about missing macros and fix them. Once I fixed all those errors, I was able to re-get my macro library from my Perforce server and all is well... so far. I deleted all macros that did what you mention above so I don't do this again.

                To be fair, my project is small and simple so who knows if that's going to be a feasible solution for others, but it worked for me. Hopefully won't hit all the other pitfalls now.
                Gooner44
                Cribbage Sample Project on MP

                Comment


                  #9
                  Originally posted by Gooner44 View Post
                  So, at least for me so far, the fix was easy. Rather than roll back since that caused other issues for some reason, I just deleted my macro library.
                  Yeah, usually there's one asset that's problematic. The one that "completed the loop". Any assets in the chain can generally be removed to unblock you, and allow for a successful load. This can help narrow down assets that are part of the circular dependency chain. Usually you only have to roll back a singular asset, but narrowing it down can be tough if you underwent a lot of changes in one session.

                  So if you hit something like this, try removing the most recent files that you've worked on. It can help narrow down the culprit(s), and help you identify the dependency chain that you may have been unaware of. This is obviously not THE solution, like I said we have a plan in place to address this, but this advice could help unblock people in the meantime. And like I said, if you figure out the dependency loop, then please let us know so that we ensure the scenario is fixed by our refactor.
                  Last edited by User-1420270633; 08-14-2014, 11:24 AM.

                  Comment


                    #10
                    Originally posted by Mike.Beach View Post
                    Yeah, usually there's one asset that's problematic. The one that "completed the loop". Any assets in the chain can generally be removed to unblock you, and allow for a successful load. This can help narrow down assets that are part of the circular dependency chain. Usually you only have to roll back a singular asset, but narrowing it down can be tough if you underwent a lot of changes in one session.

                    So if you hit something like this, try removing the most recent files that you've worked on. It can help narrow down the culprit(s), and help you identify the dependency chain that you may have been unaware of. This is obviously not THE solution, like I said we have a plan in place to address this, but this advice could help unblock people in the meantime. And like I said, if you figure out the dependency loop, then please let us know so that we ensure the scenario is fixed by our refactor.
                    I wanted to briefly add on to this that the error logs are invaluable for fixing a crash issue like this. I had one recently when porting up to 4.3 from 4.2.1, which resulted in one of the assets I had not loading its collision volumes right.

                    I was just about to roll back the project to a previous revision (which would have undone a fair amount of work) when I went through to the logs and found that it was one particular asset crashing - so I was able to revert that one single asset and get the project working again.
                    Storyteller - An immersive VR audiobook player

                    Dungeon Survival - WIP First person dungeon crawler with a focus on survival and environmental gameplay ala roguelikes

                    Comment


                      #11
                      Generally I would agree, but in my case there was no crash dump so I was lucky to have read this post the night before.
                      Gooner44
                      Cribbage Sample Project on MP

                      Comment


                        #12
                        Glad to see that a lot of good information has come out of this already. Not glad that you're having problems of course, but we're getting lots of good hints and strategies listed here!

                        Comment


                          #13
                          I posted about this a while ago and also put this into wiki in Blueprint Fundamentals.

                          I think after this thread collects enough examples and gotchas, we should compile it in a wiki page.

                          One common solution to spot this type of problem is to check if any BP got dirty flag set to need compiling again when you compile current one.

                          And make a habit to use interface implemented check is better than use a cast. also, cast to native class before you send to a event/function call can usually "break the loop."

                          if you have too many inter-dependencies, it's a good time to stop and review your approach.
                          Unreal Engine 4 Game Framework diagram for relation of all major base object types
                          Unreal Engine 4 Input Event diagram, scroll down to section Input Processing Procedural
                          Resident Evil Classic Camera
                          RPCs official document, Must Read
                          Everything you should know about replication

                          Comment


                            #14
                            i just read through this whole thread of doom , i hope my project loads fine tomorrow !
                            Although i came upon the Collision problem which will prevent the project form loading and force close it , but in the logs it says it is somehow related to collision .
                            I think it was 4.3 when they released capsule collision primitive , and my problem was exactly capsule collision primitive , i deleted the asset using it , in the project folder , and it started to look good again . i didnt use capsule collision ever again , but i think they fixed it anyway .

                            Comment


                              #15
                              We need to compile the list & make a list to feedback to Epic for future fix.
                              Check my working title: The Locked Room here: https://forums.unrealengine.com/show...he-locked-Room

                              Comment

                              Working...
                              X