From 57030441790a58d98705400d61a4be63611a2fe4 Mon Sep 17 00:00:00 2001 From: Robert Deaton Date: Wed, 12 Aug 2026 10:41:29 -0700 Subject: [PATCH 1/2] defensive checks around extractors to prevent a crash on /frm test endpoints --- .../Private/Endpoints/World/Resources.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Source/FicsitRemoteMonitoring/Private/Endpoints/World/Resources.cpp b/Source/FicsitRemoteMonitoring/Private/Endpoints/World/Resources.cpp index 65e36e91..7e8841a4 100644 --- a/Source/FicsitRemoteMonitoring/Private/Endpoints/World/Resources.cpp +++ b/Source/FicsitRemoteMonitoring/Private/Endpoints/World/Resources.cpp @@ -248,7 +248,15 @@ void UResources::getExtractor(UObject* WorldContext, FRequestData RequestData, T float CurrentProd = Productivity * MaxProd; - TSharedPtr JProduct = GetItemValueObject(ResourceClass->GetResourceClass(), ExtractorInventory->GetNumItems(ItemClass), Extractor->GetFluidInventoryStackSizeScalar()); + // GetNumItems() asserts mInventoryStacks.Num() > 0 ("Inventory need to be + // initialized before use"), so an extractor whose output inventory has not + // been initialized yet - e.g. while the world is still streaming in - takes + // the whole game down. GetSizeLinear() reads the same array without asserting. + const int32 NumItems = (IsValid(ExtractorInventory) && ExtractorInventory->GetSizeLinear() > 0) + ? ExtractorInventory->GetNumItems(ItemClass) + : 0; + + TSharedPtr JProduct = GetItemValueObject(ResourceClass->GetResourceClass(), NumItems, Extractor->GetFluidInventoryStackSizeScalar()); JProduct->Values.Add("CurrentProd", MakeShared(CurrentProd)); JProduct->Values.Add("MaxProd", MakeShared(MaxProd)); JProduct->Values.Add("ProdPercent", MakeShared(100 * UKismetMathLibrary::SafeDivide(CurrentProd, MaxProd))); @@ -314,7 +322,10 @@ void UResources::getFrackingActivator(UObject* WorldContext, FRequestData Reques const float Productivity = SatelliteExtractor->GetProductivity(); const UFGInventoryComponent* ExtractorInventory = SatelliteExtractor->GetOutputInventory(); - const float NumItems = ExtractorInventory->GetNumItems(ItemClass); + // Same uninitialised-inventory assert as getExtractor above. + const float NumItems = (IsValid(ExtractorInventory) && ExtractorInventory->GetSizeLinear() > 0) + ? ExtractorInventory->GetNumItems(ItemClass) + : 0; BaseNumItems += NumItems; float CurrentProd = Productivity * MaxProd; From afdc8aef56c8af85e79a9b3a5d4526b88497bd9e Mon Sep 17 00:00:00 2001 From: Robert Deaton Date: Wed, 12 Aug 2026 10:42:11 -0700 Subject: [PATCH 2/2] Force more endpoints onto the main thread to avoid assert crashes --- .../Private/FicsitRemoteMonitoring.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Source/FicsitRemoteMonitoring/Private/FicsitRemoteMonitoring.cpp b/Source/FicsitRemoteMonitoring/Private/FicsitRemoteMonitoring.cpp index 6b0350a3..b0ea137e 100644 --- a/Source/FicsitRemoteMonitoring/Private/FicsitRemoteMonitoring.cpp +++ b/Source/FicsitRemoteMonitoring/Private/FicsitRemoteMonitoring.cpp @@ -756,10 +756,13 @@ void AFicsitRemoteMonitoring::InitAPIRegistry() RegisterEndpoint(FAPIEndpoint("GET", "getEncoder", &UFactoryLibrary::getEncoder)); RegisterEndpoint(FAPIEndpoint("GET", "getExplorationSink", &USession::getExplorationSink)); RegisterEndpoint(FAPIEndpoint("GET", "getExplorer", &UVehicles::getExplorer).RequiresGameThread()); - RegisterEndpoint(FAPIEndpoint("GET", "getExtractor", &UResources::getExtractor)); + // RequiresGameThread: calls AFGBuildableFactory::GetProductivity(), which + // check()s IsInGameThread() and crashes when served off the uWS event loop. + RegisterEndpoint(FAPIEndpoint("GET", "getExtractor", &UResources::getExtractor).RequiresGameThread()); RegisterEndpoint(FAPIEndpoint("GET", "getFactoryCart", &UVehicles::getFactoryCart).RequiresGameThread()); RegisterEndpoint(FAPIEndpoint("GET", "getFoundry", &UFactoryLibrary::getFoundry)); - RegisterEndpoint(FAPIEndpoint("GET", "getFrackingActivator", &UResources::getFrackingActivator)); + // RequiresGameThread: same GetProductivity() game-thread check as getExtractor. + RegisterEndpoint(FAPIEndpoint("GET", "getFrackingActivator", &UResources::getFrackingActivator).RequiresGameThread()); RegisterEndpoint(FAPIEndpoint("GET", "getFuelGenerator", &UPower::getFuelGenerator)); RegisterEndpoint(FAPIEndpoint("GET", "getGeothermalGenerator", &UPower::getGeothermalGenerator)); RegisterEndpoint(FAPIEndpoint("GET", "getHazards", &UPlayerLibrary::getHazards).RequiresGameThread()); @@ -780,7 +783,10 @@ void AFicsitRemoteMonitoring::InitAPIRegistry() RegisterEndpoint(FAPIEndpoint("GET", "getPower", &UPower::getPower)); RegisterEndpoint(FAPIEndpoint("GET", "getPowerSlug", &UResources::getPowerSlug).RequiresGameThread()); RegisterEndpoint(FAPIEndpoint("GET", "getPowerUsage", &UPower::getPowerUsage)); - RegisterEndpoint(FAPIEndpoint("GET", "getProdStats", &USession::getProdStats)); + // RequiresGameThread: walks every manufacturer/extractor/generator calling + // GetProductivity(), which check()s IsInGameThread(). This was the most + // frequent startup crash - the web UI polls it on a timer. + RegisterEndpoint(FAPIEndpoint("GET", "getProdStats", &USession::getProdStats).RequiresGameThread()); RegisterEndpoint(FAPIEndpoint("GET", "getPump", &ULogistics::getPump)); RegisterEndpoint(FAPIEndpoint("GET", "getRadarTower", &USupport::getRadarTower)); RegisterEndpoint(FAPIEndpoint("GET", "getRecipes", &UResearch::getRecipes).RequiresGameThread());