#include "PlanetDataManager.h" #include "ComputePointActor.h" #include "HttpModule.h" #include "Interfaces/IHttpResponse.h" #include "Dom/JsonObject.h" #include "Dom/JsonValue.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "Engine/World.h" // Cesium coordinate conversion #include "CesiumGeoreference.h" APlanetDataManager::APlanetDataManager() { PrimaryActorTick.bCanEverTick = false; } void APlanetDataManager::BeginPlay() { Super::BeginPlay(); // Build default mock path if not overridden if (MockDataPath.IsEmpty()) { MockDataPath = FPaths::ProjectContentDir() / TEXT("Data/mock_compute_points.json"); } FetchAllData(); } void APlanetDataManager::FetchAllData() { // Clear previous actors for (AComputePointActor* Point : SpawnedPoints) { if (IsValid(Point)) Point->Destroy(); } SpawnedPoints.Empty(); if (bUseLocalMockData) { LoadMockComputePoints(); } else { FetchComputePoints(); } } // --------------------------------------------------------------------------- // Phase A: local JSON file // --------------------------------------------------------------------------- void APlanetDataManager::LoadMockComputePoints() { FString JsonStr; if (!FFileHelper::LoadFileToString(JsonStr, *MockDataPath)) { UE_LOG(LogTemp, Error, TEXT("PlanetDataManager: cannot read mock file: %s"), *MockDataPath); return; } TArray Points = ParseComputePointsJson(JsonStr); UE_LOG(LogTemp, Log, TEXT("PlanetDataManager: loaded %d points from mock file"), Points.Num()); SpawnComputePoints(Points); } // --------------------------------------------------------------------------- // Phase B: HTTP request // --------------------------------------------------------------------------- void APlanetDataManager::FetchComputePoints() { const FString Url = BackendBaseUrl + TEXT("/api/v1/ue/compute-points"); TSharedRef Req = FHttpModule::Get().CreateRequest(); Req->SetURL(Url); Req->SetVerb(TEXT("GET")); Req->SetHeader(TEXT("Content-Type"), TEXT("application/json")); Req->OnProcessRequestComplete().BindUObject( this, &APlanetDataManager::OnComputePointsResponse); Req->ProcessRequest(); UE_LOG(LogTemp, Log, TEXT("PlanetDataManager: GET %s"), *Url); } void APlanetDataManager::OnComputePointsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) { if (!bSuccess || !Response.IsValid()) { UE_LOG(LogTemp, Error, TEXT("PlanetDataManager: HTTP request failed. Is the backend running at %s?"), *BackendBaseUrl); return; } if (Response->GetResponseCode() != 200) { UE_LOG(LogTemp, Error, TEXT("PlanetDataManager: HTTP %d from %s"), Response->GetResponseCode(), *Request->GetURL()); return; } TArray Points = ParseComputePointsJson(Response->GetContentAsString()); UE_LOG(LogTemp, Log, TEXT("PlanetDataManager: received %d compute points"), Points.Num()); SpawnComputePoints(Points); } // --------------------------------------------------------------------------- // JSON parser // --------------------------------------------------------------------------- TArray APlanetDataManager::ParseComputePointsJson(const FString& JsonStr) { TArray Result; TSharedPtr Root; TSharedRef> Reader = TJsonReaderFactory<>::Create(JsonStr); if (!FJsonSerializer::Deserialize(Reader, Root) || !Root.IsValid()) { UE_LOG(LogTemp, Error, TEXT("PlanetDataManager: JSON parse failed")); return Result; } const TArray>* Items; if (!Root->TryGetArrayField(TEXT("items"), Items)) { UE_LOG(LogTemp, Error, TEXT("PlanetDataManager: no 'items' array in JSON")); return Result; } for (const TSharedPtr& Val : *Items) { const TSharedPtr* ObjPtr; if (!Val->TryGetObject(ObjPtr)) continue; const TSharedPtr& Obj = *ObjPtr; FComputePoint Pt; Obj->TryGetStringField(TEXT("id"), Pt.Id); Obj->TryGetStringField(TEXT("name"), Pt.Name); Obj->TryGetStringField(TEXT("country"), Pt.Country); Obj->TryGetStringField(TEXT("city"), Pt.City); double Lat, Lon; if (!Obj->TryGetNumberField(TEXT("latitude"), Lat)) continue; if (!Obj->TryGetNumberField(TEXT("longitude"), Lon)) continue; Pt.Latitude = (float)Lat; Pt.Longitude = (float)Lon; int32 Rank; if (Obj->TryGetNumberField(TEXT("rank"), Rank)) Pt.Rank = Rank; double Rmax; if (Obj->TryGetNumberField(TEXT("rmax_tflops"), Rmax)) Pt.RmaxTFlops = (float)Rmax; double Rpeak; if (Obj->TryGetNumberField(TEXT("rpeak_tflops"), Rpeak)) Pt.RpeakTFlops = (float)Rpeak; int32 Cores; if (Obj->TryGetNumberField(TEXT("cores"), Cores)) Pt.Cores = Cores; double Power; if (Obj->TryGetNumberField(TEXT("power_kw"), Power)) Pt.PowerKw = (float)Power; Result.Add(Pt); } return Result; } // --------------------------------------------------------------------------- // Spawn actors // --------------------------------------------------------------------------- void APlanetDataManager::SpawnComputePoints(const TArray& Points) { if (!ComputePointClass) { UE_LOG(LogTemp, Warning, TEXT("PlanetDataManager: ComputePointClass not set — set it in the Details Panel")); return; } UWorld* World = GetWorld(); if (!World) return; // Find the CesiumGeoreference in the level ACesiumGeoreference* Georeference = ACesiumGeoreference::GetDefaultGeoreference(World); if (!Georeference) { UE_LOG(LogTemp, Error, TEXT("PlanetDataManager: no CesiumGeoreference in level. Add a CesiumGeoreference actor.")); return; } for (const FComputePoint& Pt : Points) { // Convert geographic coordinates to Unreal world coordinates // Altitude is PointAltitudeMeters above sea level FVector WorldPos = Georeference->TransformLongitudeLatitudeHeightPositionToUnreal( FVector(Pt.Longitude, Pt.Latitude, PointAltitudeMeters) ); FActorSpawnParameters Params; Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; AComputePointActor* Actor = World->SpawnActor( ComputePointClass, WorldPos, FRotator::ZeroRotator, Params); if (Actor) { Actor->SetPointData(Pt); SpawnedPoints.Add(Actor); } } OnComputePointsLoaded.Broadcast(SpawnedPoints.Num()); UE_LOG(LogTemp, Log, TEXT("PlanetDataManager: spawned %d compute point actors"), SpawnedPoints.Num()); }