Converting blueprint track generator to C++ - built lighting problem

I have converted the (very useful) blueprint track generator (“Blueprint Splines Track” in the “Learn” section of the Epic Games Launcher) into C++ and it works absolutely fine apart from one problem.

Every time I relaunch the editor and click on “play in editor”, I receive a message stating that the lighting has to be rebuilt, even though I have built the lighting immediately prior to saving. This problem does not arise in the original Blueprint version. I would like to ensure that built lighting is applied correctly to the track and environment on reloading.

I have set out the relevant C++ code which generates the track below. Note that the splinemesh components are created in the “OnConstruction” method. I suspect that the editor views these procedurally generated components as brand new components on a reload, despite no modifications being made to the underlying spline.

Does anyone have any suggestions as to (i) why this does not work in the same way as a Blueprint construction script; and (ii) how the code can be modified so that the procedurally generated components are saved and loaded correctly, thereby maintaining the built lighting?

Edit: I have come across this post from September 2014 which was effectively the same problem and doesn’t appear to have been properly resolved. However, I would still be grateful to hear from any of the Epic team as to whether there have been any changes to the Unreal codebase since then or any new workarounds which enable this problem to be solved. It seems strange that Blueprint, in this case, has more powerful functionality than C++!

// Constructor
ACPP_TrackSpline::ACPP_TrackSpline():
	m_splineComponent(nullptr),
	m_bLoopingTrack(false)
{
	PrimaryActorTick.bCanEverTick = false;

	m_splineComponent = CreateDefaultSubobject<USplineComponent>(TEXT("SplineComponent"));
	m_splineComponent->SetMobility(EComponentMobility::Static);
	RootComponent = m_splineComponent;
}

// Construction script - calls buildTrack()
void ACPP_TrackSpline::OnConstruction(const FTransform& Transform)
{
	buildTrack();
}

// Function to create the splinemesh geometry for the track
void ACPP_TrackSpline::buildTrack()
{
	int32 splinePointCount = m_splineComponent->GetNumSplinePoints();
	m_aTrackSplineData.SetNum(splinePointCount);

	int32 lastIndex = m_bLoopingTrack ? splinePointCount - 1 : splinePointCount - 2;

	for (int32 currentTrackPoint = 0; currentTrackPoint <= lastIndex; ++currentTrackPoint)
	{
		// Using a modulo operator means that if we try to access a point after the end of the spline, it will wrap around to 0
		int32 nextTrackPoint = (currentTrackPoint + 1) % splinePointCount;

		// Construct spline mesh and attach to root component
		USplineMeshComponent* splineMesh = ConstructObject<USplineMeshComponent>(USplineMeshComponent::StaticClass(), this);
		splineMesh->CreationMethod = EComponentCreationMethod::UserConstructionScript;
		splineMesh->SetMobility(EComponentMobility::Static);
		splineMesh->AttachTo(RootComponent);
		splineMesh->SetStaticMesh(m_aTrackSplineData[currentTrackPoint].m_staticMesh);
		splineMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);

		splineMesh->bCastDynamicShadow = false;

		// Set width of the mesh 
		splineMesh->SetStartScale(FVector2D(m_aTrackSplineData[currentTrackPoint].m_trackWidth, m_aTrackSplineData[currentTrackPoint].m_trackHeight));
		splineMesh->SetEndScale(FVector2D(m_aTrackSplineData[nextTrackPoint].m_trackWidth, m_aTrackSplineData[nextTrackPoint].m_trackHeight));

		// Set position and tangent of start and end points
		FVector pointLocationStart, pointTangentStart, pointLocationEnd, pointTangentEnd;
		m_splineComponent->GetLocalLocationAndTangentAtSplinePoint(currentTrackPoint, pointLocationStart, pointTangentStart);
		m_splineComponent->GetLocalLocationAndTangentAtSplinePoint(nextTrackPoint, pointLocationEnd, pointTangentEnd);

		splineMesh->SetStartAndEnd(pointLocationStart, pointTangentStart, pointLocationEnd, pointTangentEnd);

		// Set roll
		splineMesh->SetStartRoll(m_aTrackSplineData[currentTrackPoint].m_trackBank);
		splineMesh->SetEndRoll(m_aTrackSplineData[nextTrackPoint].m_trackBank);

		// Finish and register new component
		FinishAndRegisterComponent(splineMesh);
	}
}

#if WITH_EDITOR
void ACPP_TrackSpline::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
	DestroyConstructedComponents();
	buildTrack();
}

void ACPP_TrackSpline::PostEditMove(bool bFinished)
{
	DestroyConstructedComponents();
	buildTrack();
}
#endif

+1 to this. This is a real and definite issue, and I cannot find a solution anywhere.