TRASHCLASS issues

Been struggling with TRASHCLASS issues for a few days now. The issue is similar or the same as referenced quite a few places:
https://udn.unrealengine.com/questions/265552/trashclass-and-function-override.html

Basically modifying a base function’s input arguments in class A, that is overridden in subclasses, breaks in class C (With a hierarchy A → B → C).
With an error message like:

Error This blueprint (self) is not a TRASHCLASS_A, therefore ’ Target ’ must have a connection

Compiling A or B fixes the error in C, but restarting the editor will make the issue return, and packaging always fails.
We’ve also tried to just create a new function so we don’t modify the original function, but the new function gets the same issue.

TJ Ballard claims that the issue has been resolved in

This issue is quite a setback for us, as the only option besides getting it fixed is to recreate large portions of our blueprints. And we’re not certain that will avoid the bug in the end.

Any ETA on when we can expect a fix? Or can we get a reference to the changelist so we can apply the fix ourselves?

Hi Nicolas,

So, I would guess this is a cyclic dependency issue (probably a parent/child cyclic dependency chain). What is most likely happening is that C is being compiled as A is being regenerated. It is validating connections that haven’t been fixed up yet (ones that are left referencing an old, now intermediate/defunct, class).

This is a problem that is cropping up more and more. Since we fixed up cyclic dependency load crashes, we’ve seen an increase in this type of error (probably means this would have just crashed before). We’re slated to address it on the whole, but fixing up individual issues has become a lot like whack-a-mole (fix one, and you’ve just undid another fix). There are a lot of causes (a lot of things that bring in different dependencies), which is why one got fixed but you’re still seeing this.

The proper solution is a refactor of our compiler. We need to turn it into a multi step (well divided) process (something more akin to compilation and then linking). As you can imagine though, this is a lengthy task that first requires a well thought out design and then a lot of work. In the meantime, I’d suggest you restructure your content a bit and untangle the dependency chain. The easiest way to do this is to break in UBlueprint::RegenerateClass() for C, and then look at the LoadPackageInternal() calls in the callstack; this will help you identify what all is introducing these dependencies.

Hope this helps!

That makes sense. That would be a big task, but also a very important one. I hope you will start on it soon.

There are about 40-41 LoadPackageInternal in my callstack, with a lot of classes being visited multiple times. It’s not an easy task to figure out how it all works. I have a couple guesses, but I haven’t figured out how to read this to figure out the circular dependency. I’m going to try reproducing in a smaller project and see if it makes more sense there when I have time.
Please share if you have any more tips or guidance on what to look for. I’ll comment again later with my results.

We’ve been running into this issue quite often lately as well. I’ve had a very hard time untangling our dependencies by stepping through the code. The base class of the class that is exhibiting the trashclass error isn’t listed in that classes ImportMap as I would expect based upon the above explanation. When the trashclass error is detected, does the engine know about the cyclic dependency? If so, would it be possible to print the cycle out as a stop gap until we get a new blueprint compiler?

Yeah, it can be hard to get you head around this stuff - when I was fixing up cyclic load crash issues I’d have to write out the whole callstack on a whiteboard just to try and follow what was happening. Thankfully you shouldn’t need the deep of an understanding here.

LoadPackageInternal() is set up to be reentrant (as you’ve whitnessed); when you load an asset through this it will eventually ends up in VerifyImport or LoadAllObjects. The package’s ImportMap is a list of things it depends on (like other Blueprint packages, native classes etc.). As part of the load it loops over them, attempting to load those as well. When LoadPackageInternal() is reentered, the earlier entries in the ImportMap will have been loaded already so it continues from where it last left off (further up the callstack). This is a high level picture of the process; one that I don’t think you necessarily need to know, but it doesn’t hurt to have the extra information.

Mostly seeing what all is being loaded by LoadPackageInternal() is helpful. Dependencies are sometimes hard to identify by picking apart the Blueprint (especially for ones that are several degrees of separation). This at least gives you a clear picture of what all depends on what. You can now tell, say that A depends on X (X is listed in A’s ImportMap), and X depends on Y, and Y depends on C - there you have a cyclic dependency; A depends on C in some one off fashion, and C depends on A (since C is a sub-class of A).

I would mostly start with where A or C first shows up in the callstack, and follow along with the dependencies in content - understand what is bringing in the dependency to the next link in the chain (knowing what you’re looking for makes this easier). As you follow along, you can diagnose where it’s best to break the chain (can a direct reference be replaced with and interface, or an event or something - something that’ll decouple the two Blueprints) - this is up to you.

Hope this gives you at least a little guidance in the process.

I can’t speak for your specific scenario, but generally the compiler is somewhat unaware of the full dependency chain. Seeing your callstack here may be helpful, but I presume somewhere up the stack A is in the midst of compiling. If you’re seeing the error in C, then there may be some one off dependency that is linking the two (hence why you don’t see A directly in C’s ImportMap). Your callstack may look something like this:

  • Error: TRASHCLASS
  • Compile C (relies on A)
  • Update X’s dependencies (C)
  • Compile X (dependency of A, depends on C)
  • Update A’s dependencies (X)
  • Compile A

Here, you can see that the dependency chain that leads to C is A → X → C. You can track that externally if you want and spit it out with the error.

Alternatively, you can put a temporary log in LoadPackageInternal() tracking the depth and what InLongPackageName is - this will be very verbose (especially if your content as interwoven as it seems), but you can scope this temporary logging such that it only hits for your targeted Blueprints. We’ve had others that do this with success. This is still my go to place for understanding the dependency chain.

OK, thanks Mike, I’ll try giving that a shot. I’ve managed to work around it for now by not calling the function that this error was being triggered on. Obviously that’s not a real solution so when this issue crops up again in our project I’ll give the printing the package name a shot.