@Daniel I released another mod, Consumption At Start. I consider this an overall improvement to the game, so suggest to considering to take this over into the main game.
But the reason I’m mentioning you is not because of that, but because I found several things while creating this mod. Two tiny bugs I highlighted in separate threads. But there is another issue I learned about that you should pay attention to:
There is an inaccuracy in FactoryProductionLogic.UpdateSimulationThread()
. This causes production times to longer by about 10 frames than they are supposed to. For factories with slow cycles this is hardly relevant, but for fast ones (5s, or even faster with efficiency boosts) this amounts to about 4% slower output than you are advertising.
I made a setup with just a sand mine (5s cycle) on a basically empty map, so frame rate was constant. I measured about 10 cycles as accurately as my reflexes allowed, and the real time was always longer than 5s, usually it was around 5.2s. The 200ms is equal to the 10 simulation frames I mentioned. I tried to fix the deficiency with my mod, too, and measured again. The times were spot on 5s, or very slightly around that (measurement inaccuracy).
The problem is here:
if (productionTimeStep < productionDefinition.timeSteps)
{
productionTimeStep = Mathf.Clamp(productionTimeStep + possibleProgressPer10Frames, 0, productionDefinition.timeSteps * 2);
lastProductionSteps = possibleProgressPer10Frames;
}
else if (!OutgoingStorageFull())
{
Produce();
productionTimeStep = 0;
}
When a production cycle is finished, you simply set the productionTimeStep
to 0. This doesn’t account for the fact that production in those 10 frames may have exceeded 100% progress. E.g. if it was at 99.95% in the last update and afterwards it is at 100.45%, then you basically wasted 0.45% of progress. This would be no problem for a sand mine operating at 100% efficiency though, because it makes 1000 progress every 10 frames and needs exactly 25000, so it always hits the number spot on. A second effect is that you need to check for the progress immediately after you added the progress. The current code waits until the next update to check for progress. These 10 frames are wasted, waiting at 100% progress. I think you can even see that on the progress bar if you have very quick eyes. There is a tiny stutter at 100%.
For reference, my implementation:
Code
I also needed to change Produce()
and Building.UpdateFrameThread()
, but FactoryProductionLogic.UpdateSimulationThread()
is the meat of the action.
byte district = WorldScripts.Inst.districtsManager.GetDistrict(__instance.consumerProducer.building.GetPosition());
__instance.consumerProducer.lastStepPowerProduced = 0f;
int possibleProgressPer10Frames = Mathf.RoundToInt(100f * __instance.GetEfficiency()) * 10;
___lastProductionSteps = 0;
if ( ( ( __instance.logicOverride != "spaceship" ) ||
( __instance.productionDefinition.consumables.Count > 0 ) ) &&
( ( !__instance.HasTerraformingLogicOverride() ) ||
( __instance.consumerProducer.building.HasPower() ) ) &&
( !__instance.OutgoingStorageFull() ) &&
( ( ___productionTimeStep > 0 ) ||
( __instance.IncomingStorageFulfilled() ) ) )
{
__instance.couldWork = true;
if (___productionTimeStep == 0 && possibleProgressPer10Frames > 0)
{
ModifyIncomingStorage(__instance, -1);
}
___productionTimeStep += Mathf.Min(possibleProgressPer10Frames, __instance.productionDefinition.timeSteps);
___lastProductionSteps = possibleProgressPer10Frames;
if (___productionTimeStep >= __instance.productionDefinition.timeSteps)
{
__instance.Produce();
if (__instance.IncomingStorageFulfilled())
{
// immediately produce again
ModifyIncomingStorage(__instance, -1);
___productionTimeStep = Mathf.Max(___productionTimeStep - __instance.productionDefinition.timeSteps, 1); // 1 minimum to mark that resources were already spent
}
else
{
// no resources, reset progress to 0
___productionTimeStep = 0;
}
}
__instance.consumerProducer.lastStepPowerProduced = __instance.productionDefinition.powerOutput;
if (__instance.consumerProducer.lastStepPowerProduced > 0f)
{
__instance.consumerProducer.lastStepPowerProduced *= __instance.GetEfficiencyExceptPower();
}
}
else
{
__instance.couldWork = false;
if (__instance.HasTerraformingLogicOverride())
{
Old.buildingModule.terraformerUpdater.UpdateEfficiency(__instance.consumerProducer.building.GetID(), 0f);
}
}
double productionFactor = 5 * 60 * possibleProgressPer10Frames / (double)__instance.productionDefinition.timeSteps;
foreach (ResourceCost consumable in __instance.productionDefinition.consumables)
{
Old.GetSimulator().market.AddConsumptionPerSecond(consumable.resource, consumable.amount * productionFactor, district);
}
foreach (ResourceCost producable in __instance.productionDefinition.producables)
{
Old.GetSimulator().market.AddProductionPerSecond(producable.resource, (double)producable.amount * productionFactor, district);
}
__instance.consumerProducer.lastStepPowerNeeded = (float)GetPowerNeeded.Invoke(__instance, null);