Instanced foliage creation is editor only?

The AInstancedFoliageActor is typically something that is defined in the editor using the foliage tool and then those get packaged into the level and restored during play. However, it seems to me that since they can be modified (new instances added, unwanted removed - at least according to another post I saw recently) if you happened to have the actor reference so why not also allow for the dynamic creation of those actors instead of hiding the implementation (CreateImplementation being called when loaded, and within the editor - but lack of one is the only thing seemingly keeping it all from being dynamic). Of course all the instance manipulation I see appears to be in-editor only, so I’m not really sure how people must be modding the instances.

I would very much like to stream foliage and not rely on any fixed placement strategies/tools from the editor (100% my own algorithms) but it seems that at the very least if I wanted an efficient method for rendering foliage in a large open-world context that my only choice would be to pre-create these actors for each cell. For a large world, that is a lot of wasted resources.

Before I go engine modding my way into this (I really don’t want to) can anyone point to faults in my logic above?

Well, although for this I need a couple of minor things exposed outside of the editor the following code will work at runtime (in PIE - if manual exposure is not implemented) to spawn instanced foliage programmatically. So my new question - is there a process for requesting that editor only shielded methods be exposed? In my two instances I think they were just being overzealous and they probably can be exposed without issue. This logic assumes the component you are placing on has been streamed in & visible already, if you don’t get a landscape hit it will not render.

	FActorSpawnParameters parms;
	parms.ObjectFlags = RF_Transient;
	parms.CustomPreSpawnInitalization = [&](AActor* actor) {
		auto FoliageActor = Cast<AInstancedFoliageActor>(actor);

		FoliageActor->bNetStartup = true;
		FoliageActor->bExchangedRoles = false;
		FoliageActor->SetLockLocation(true);
	
		for (auto it = foliageMap_.CreateConstIterator(); it; ++it) {
			auto & FoliageInfo = FoliageActor->AddFoliageInfo(it->Value);

			// NOTE: These two are editor only and need to be exposed
			FoliageInfo->CreateImplementation(it->Value);
			FoliageInfo->Implementation->Initialize(it->Value);
		}

		for (int i = 0; i < 8; i++) {
			auto FoliageInfo = FoliageActor->FindInfo(FoliageType);
			if (FoliageInfo != nullptr) {
				FFoliageInstance NewFoliageInstance = FFoliageInstance();
				NewFoliageInstance.Flags = 0;
				auto newPos = position + i*500;
				NewFoliageInstance.Location = FVector(newPos.X - position.X, newPos.Y - position.Y, 0.0f);

				FTransform InstanceToWorld = NewFoliageInstance.GetInstanceWorldTransform();
				FVector Down(-FVector::UpVector);
				FVector Start(newPos.X, newPos.Y, 2000000.0);
				FVector End(newPos.X, newPos.Y, -2000000.0);

				FHitResult Result;
				bool bHit = GetWorld()->LineTraceSingleByObjectType(Result, Start, End, FCollisionObjectQueryParams(ECC_WorldStatic), FCollisionQueryParams(NAME_None, FCollisionQueryParams::GetUnknownStatId(), true));

				if (bHit && Result.Component.IsValid() && Result.Component->IsA(ULandscapeHeightfieldCollisionComponent::StaticClass()))
				{
					NewFoliageInstance.Location.Z = Result.ImpactPoint.Z;
					auto fsm = reinterpret_cast<FFoliageStaticMesh*>(FoliageInfo->Implementation.Get());
					auto hism = fsm->Component.Get();
					hism->AddInstance(NewFoliageInstance.GetInstanceWorldTransform(), true);
				}
			}
		}
	};

	auto FoliageActor = GetWorld()->SpawnActor<AInstancedFoliageActor>(AInstancedFoliageActor::StaticClass(), position, rotation, parms);