static void Render_supplies(Panel p, Vessel v, VesselData vd, VesselResources resources) { int supplies = 0; // for each supply foreach (Supply supply in Profile.supplies) { // get resource info ResourceInfo res = resources.GetResource(v, supply.resource); // only show estimate if the resource is present if (res.Capacity <= 1e-10) { continue; } // render panel title, if not done already if (supplies == 0) { p.AddSection(Local.TELEMETRY_SUPPLIES); //"SUPPLIES" } // determine label var resource = PartResourceLibrary.Instance.resourceDefinitions[supply.resource]; string label = Lib.SpacesOnCaps(resource.displayName).ToLower(); StringBuilder sb = new StringBuilder(); sb.Append("<align=left />"); if (res.AverageRate != 0.0) { sb.Append(Lib.Color(res.AverageRate > 0.0, Lib.BuildString("+", Lib.HumanReadableRate(Math.Abs(res.AverageRate))), Lib.Kolor.PosRate, Lib.BuildString("-", Lib.HumanReadableRate(Math.Abs(res.AverageRate))), Lib.Kolor.NegRate, true)); } else { sb.Append("<b>"); sb.Append(Local.TELEMETRY_nochange); //no change sb.Append("</b>"); } if (res.AverageRate < 0.0 && res.Level < 0.0001) { sb.Append(" <i>"); sb.Append(Local.TELEMETRY_empty); //(empty) sb.Append("</i>"); } else if (res.AverageRate > 0.0 && res.Level > 0.9999) { sb.Append(" <i>"); sb.Append(Local.TELEMETRY_full); //(full) sb.Append("</i>"); } else { sb.Append(" "); // spaces to prevent alignement issues } sb.Append("\t"); sb.Append(res.Amount.ToString("F1")); sb.Append("/"); sb.Append(res.Capacity.ToString("F1")); sb.Append(" ("); sb.Append(res.Level.ToString("P0")); sb.Append(")"); List <SupplyData.ResourceBrokerRate> brokers = vd.Supply(supply.resource).ResourceBrokers; if (brokers.Count > 0) { sb.Append("\n<b>------------ \t------------</b>"); foreach (SupplyData.ResourceBrokerRate rb in brokers) { sb.Append("\n"); sb.Append(Lib.Color(rb.rate > 0.0, Lib.BuildString("+", Lib.HumanReadableRate(Math.Abs(rb.rate)), " "), Lib.Kolor.PosRate, // spaces to mitigate alignement issues Lib.BuildString("-", Lib.HumanReadableRate(Math.Abs(rb.rate)), " "), Lib.Kolor.NegRate, // spaces to mitigate alignement issues true)); sb.Append("\t"); sb.Append(rb.broker.Title); } } string rate_tooltip = sb.ToString(); // finally, render resource supply p.AddContent(label, Lib.HumanReadableDuration(res.DepletionTime()), rate_tooltip); ++supplies; } }
/// <summary>synchronize resources from cache to vessel</summary> /// <remarks> /// this function will also sync from vessel to cache so you can always use the /// ResourceInfo interface to get information about resources /// </remarks> public void Sync(Vessel v, VesselData vd, double elapsed_s, List <PartResource> loadedResList, List <ProtoPartResourceSnapshot> unloadedResList) { UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.Resource.Sync"); // # OVERVIEW // - consumption/production is accumulated in "Deferred", then this function called // - save previous step amount/capacity // - part loop 1 : detect new amount/capacity // - if amount has changed, this mean there is non-Kerbalism producers/consumers on the vessel // - if non-Kerbalism producers are detected on a loaded vessel, prevent high timewarp rates // - clamp "Deferred" to amount/capacity // - part loop 2 : apply "Deferred" to all parts // - apply "Deferred" to amount // - calculate change rate per-second // - calculate resource level // - reset deferred // # NOTE // It is impossible to guarantee coherency in resource simulation of loaded vessels, // if consumers/producers external to the resource cache exist in the vessel (#96). // Such is the case for example on loaded vessels with stock solar panels. // The effect is that the whole resource simulation become dependent on timestep again. // From the user point-of-view, there are two cases: // - (A) the timestep-dependent error is smaller than capacity // - (B) the timestep-dependent error is bigger than capacity // In case [A], there are no consequences except a slightly wrong computed level and rate. // In case [B], the simulation became incoherent and from that point anything can happen, // like for example insta-death by co2 poisoning or climatization. // To avoid the consequences of [B]: // - we hacked the solar panels to use the resource cache (SolarPanelFixer) // - we detect incoherency on loaded vessels, and forbid the two highest warp speeds // remember vessel-wide amount currently known, to calculate rate and detect non-Kerbalism brokers double oldAmount = Amount; // remember vessel-wide capacity currently known, to detect flow state changes double oldCapacity = Capacity; // iterate over all enabled resource containers and detect amount/capacity again // - this detect production/consumption from stock and third-party mods // that by-pass the resource cache, and flow state changes in general Amount = 0.0; Capacity = 0.0; if (v.loaded) { foreach (PartResource r in loadedResList) { Amount += r.amount; Capacity += r.maxAmount; } } else { foreach (ProtoPartResourceSnapshot r in unloadedResList) { Amount += r.amount; Capacity += r.maxAmount; } } // As we haven't yet synchronized anything, changes to amount can only come from non-Kerbalism producers or consumers double unsupportedBrokersRate = Amount - oldAmount; // Avoid false detection due to precision errors if (Math.Abs(unsupportedBrokersRate) < 1e-05) { unsupportedBrokersRate = 0.0; } // Calculate the resulting rate unsupportedBrokersRate /= elapsed_s; // Detect flow state changes bool flowStateChanged = Capacity - oldCapacity > 1e-05; // clamp consumption/production to vessel amount/capacity // - if deferred is negative, then amount is guaranteed to be greater than zero // - if deferred is positive, then capacity - amount is guaranteed to be greater than zero Deferred = Lib.Clamp(Deferred, -Amount, Capacity - Amount); // apply deferred consumption/production to all parts, simulating ALL_VESSEL_BALANCED // - iterating again is faster than using a temporary list of valid PartResources // - avoid very small values in deferred consumption/production if (Math.Abs(Deferred) > 1e-10) { if (v.loaded) { foreach (PartResource r in loadedResList) { // calculate consumption/production coefficient for the part double k = Deferred < 0.0 ? r.amount / Amount : (r.maxAmount - r.amount) / (Capacity - Amount); // apply deferred consumption/production r.amount += Deferred * k; } } else { foreach (ProtoPartResourceSnapshot r in unloadedResList) { // calculate consumption/production coefficient for the part double k = Deferred < 0.0 ? r.amount / Amount : (r.maxAmount - r.amount) / (Capacity - Amount); // apply deferred consumption/production r.amount += Deferred * k; } } } // update amount, to get correct rate and levels at all times Amount += Deferred; // reset deferred production/consumption Deferred = 0.0; // recalculate level Level = Capacity > 0.0 ? Amount / Capacity : 0.0; // calculate rate of change per-second // - don't update rate during warp blending (stock modules have instabilities during warp blending) // - ignore interval-based rules consumption/production if (!v.loaded || !Kerbalism.WarpBlending) { Rate = (Amount - oldAmount - intervalRuleAmount) / elapsed_s; } // calculate average rate of change per-second from interval-based rules intervalRulesRate = 0.0; foreach (var rb in intervalRuleBrokersRates) { intervalRulesRate += rb.Value; } // AverageRate is the exposed property that include simulated rate from interval-based rules. // For consistency with how "Rate" is calculated, we only add the simulated rate if there is some capacity or amount for it to have an effect AverageRate = Rate; if ((intervalRulesRate > 0.0 && Level < 1.0) || (intervalRulesRate < 0.0 && Level > 0.0)) { AverageRate += intervalRulesRate; } // For visualization purpose, update the VesselData.supplies brokers list, merging all detected sources : // - normal brokers that use Consume() or Produce() // - "virtual" brokers from interval-based rules // - non-Kerbalism brokers (aggregated rate) vd.Supply(ResourceName).UpdateResourceBrokers(brokersResourceAmounts, intervalRuleBrokersRates, unsupportedBrokersRate, elapsed_s); //Lib.Log("RESOURCE UPDATE : " + v); //foreach (var rb in vd.Supply(ResourceName).ResourceBrokers) // Lib.Log(Lib.BuildString(ResourceName, " : ", rb.rate.ToString("+0.000000;-0.000000;+0.000000"), "/s (", rb.name, ")")); //Lib.Log("RESOURCE UPDATE END"); // reset amount added/removed from interval-based rules IntervalRuleHappened = intervalRuleAmount > 0.0; intervalRuleAmount = 0.0; // if incoherent producers are detected, do not allow high timewarp speed // - can be disabled in settings // - unloaded vessels can't be incoherent, we are in full control there // - ignore incoherent consumers (no negative consequences for player) // - ignore flow state changes (avoid issue with process controllers and other things) if (Settings.EnforceCoherency && v.loaded && TimeWarp.CurrentRate > 1000.0 && unsupportedBrokersRate > 0.0 && !flowStateChanged) { Message.Post ( Severity.warning, Lib.BuildString ( !v.isActiveVessel ? Lib.BuildString("On <b>", v.vesselName, "</b>\na ") : "A ", "producer of <b>", ResourceName, "</b> has\n", "incoherent behavior at high warp speed.\n", "<i>Unload the vessel before warping</i>" ) ); Lib.StopWarp(1000.0); } // reset brokers brokersResourceAmounts.Clear(); intervalRuleBrokersRates.Clear(); // reset amount added/removed from interval-based rules intervalRuleAmount = 0.0; UnityEngine.Profiling.Profiler.EndSample(); }