Why does my created UTexture2D suddenly become invalid?

What I’m doing:

Creating a UTexture2D from an FColor array at runtime using the following:

// Create the texture
			mTexture = UTexture2D::CreateTransient((int32)textureSize.X, (int32)textureSize.Y, PF_B8G8R8A8);
			mTexture->Filter = TextureFilter::TF_Nearest;

			// Lock the texture so it can be modified
			mMipData = static_cast<FColor*>(mTexture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE));

			// Create base mip from the pixel color array
			for (int32 y = 0; y < mTexture->PlatformData->Mips[0].SizeY; y++)
			{
				for (int32 x = 0; x < mTexture->PlatformData->Mips[0].SizeX; x++)
				{
					int32 curPixelIndex = (y * mTexture->PlatformData->Mips[0].SizeX) + x;
					mMipData[curPixelIndex] = sourceColors[curPixelIndex];
				}
			}

			//// Unlock the texture
			mTexture->PlatformData->Mips[0].BulkData.Unlock();
			mTexture->UpdateResource();

What’s wrong:

This texture acts as the minimap for my game, so while playing I change the values of specific pixels to reflect the state of that tile in the game. This all works great for the first ~2 minutes. Then, my created texture suddenly becomes invalid.

What I think the problem is:

I expect that this is related in some way to the “Transient” in “CreateTransient,” specifically the fact that the new texture is constructed with the transient package as it’s Outer and with the RF_Transient flag, like so:

  NewTexture = ConstructObject<UTexture2D>(
                UTexture2D::StaticClass(),
                GetTransientPackage(),
                NAME_None,
                RF_Transient
                );

Everywhere I have checked suggests that this transient-ness only affects saving, which is fine, as I only need the created texture for the duration of the round. But it appears there’s more to it. I don’t know how often the GC is firing, but my suspicion is that it’s collecting my created texture when it does.

My questions:

  1. What is the purpose/significance of the “Outer” parameter when an object is constructed?
  2. What is the “transient package”?
  3. Is my assumption correct that the transient Outer is causing the texture to invalidate after a couple minutes?
    a. If so, is there a way to change the outer to prevent the invalidation? Or some other way to prevent the invalidation?
    b. If not, what else might be causing this invalidation to occur?

Thanks!

Solved the issue, the UTexture2D* member wasn’t marked as a UPROPERTY so it was indeed being GC’d right out from under me. So the transient-ness of the texture was not the issue after all.

If anyone has a moment, I’d still love to get a little more info regarding the ramifications of having the transient package as the Outer property of a UObject.

Did you eventually find an answer to that question about having the transient package as the Outer?

I did indeed!

Just about every UObject has an Outer - generally this means the object that it lives within. In a lot of cases, especially during the actual game, this isn’t really all that important. The outer of an object has nothing to do with the lifetime of that object from a GC perspective. However, the object’s reference to its outer will be counted by the garbage collector. So as long as the object is alive, its outer will also be kept alive.

An exception to the outer rule is the UPackage. The UPackage is the root “thing” that actually gets saved and loaded by the engine, and therefore has no outer. The object outer’d to that package, though, will be saved along with that package. So in this case, the outer relationship is very important.

Take a Blueprint asset - let’s say it’s named BP_Asset.uasset. BP_Asset is the package that actually gets saved, and within that package is the actual UClass defined by your Blueprint. That’s why you may have seen “package paths” that look like “Game/Blueprints/BP_Asset.BP_Asset_C”. BP_Asset_C is the class, BP_Asset is the package.

For the case of the UTexture2D created via UTexture2D::CreateTransient() - the whole idea there is that it won’t ever be saved (aka transient). It’s just a texture out in space, so to speak. If it goes away, cool. If it’s alive, it shouldn’t be responsible for keeping anything else alive along with it.

For these cases, there is the Transient Package. When something is outer’d to the transient package, you are basically saying what we did for the texture: the object will never be saved and is not dependent on any other object being alive along with it.

Hope that clears things up a bit!

Transient means temporary… PersistantLevel would be the other outer.

Outers were used more earlier on. Now the idea of parent / outer has split, and changed somewhat. But everything needs to be referenced or part of the root set, or it will get garbage collected.

Will give you upvote, since you ask very nice question in very nice format. Good job.