Search the Community
Showing results for tags 'fixedupdate'.
-
Hello everyone! I seem to have run into a problem with a plugin I'm writing that is going from OnFixedUpdate to FixedUpdate and I was hoping someone here might have an idea why a null exception is being thrown. Background: I noticed that OnFixedUpdate was only updated when the part's stage is activated. Since I want the part to be active whether or not it has been staged (i.e. always active), I thought I would try the Unity method FixedUpdate instead. Each of my modules has a master and a slave field to detail what logic should be run on it (master runs the "main" code once/update; slaves do nothing). Testing Method: I built a small craft with 2x of my module, an engine, a parachute, and a command pod. I launched it from the pad and recovered it then looked at the KSP.log file. I repeated using FixedUpdate and took a look at the KSP.log file and have a portion of these copied below Results: Here is a comparison of the KSP.log files with OnFixedUpdate and FixedUpdate using a craft with 2 of my modules (so OnFixedUpdate/FixedUpdate should run twice per update: once as a slave, once as a master) The only change made was renaming the method and the removal/inclusion of "override" modifier of the method: With OnFixedUpdate: [LOG 12:33:35.957] [CivilianPop Revamp]Starting FixedUpdate! [LOG 12:33:35.957] [CivilianPop Revamp]Master Status: TrueSlave Status: False [LOG 12:33:35.957] [CivilianPop Revamp]Finished FixedUpdate! [LOG 12:33:35.957] [CivilianPop Revamp]Starting FixedUpdate! [LOG 12:33:35.957] [CivilianPop Revamp]Master Status: FalseSlave Status: True [LOG 12:33:35.957] [CivilianPop Revamp]Finished FixedUpdate! [LOG 12:33:36.011] [CivilianPop Revamp]Starting FixedUpdate! [LOG 12:33:36.011] [CivilianPop Revamp]Master Status: TrueSlave Status: False [LOG 12:33:36.011] [CivilianPop Revamp]Finished FixedUpdate! [LOG 12:33:36.011] [CivilianPop Revamp]Starting FixedUpdate! [LOG 12:33:36.011] [CivilianPop Revamp]Master Status: FalseSlave Status: True [LOG 12:33:36.011] [CivilianPop Revamp]Finished FixedUpdate! With FixedUpdate: [LOG 12:37:34.804] [CivilianPop Revamp]Starting FixedUpdate! [LOG 12:37:34.804] [CivilianPop Revamp]Master Status: TrueSlave Status: False [LOG 12:37:34.804] [CivilianPop Revamp]Finished FixedUpdate! [LOG 12:37:34.804] [CivilianPop Revamp]Starting FixedUpdate! [LOG 12:37:34.804] [CivilianPop Revamp]Master Status: FalseSlave Status: True [LOG 12:37:34.804] [CivilianPop Revamp]Finished FixedUpdate! [LOG 12:37:34.848] [CivilianPop Revamp]Starting FixedUpdate! [LOG 12:37:34.848] [CivilianPop Revamp]Master Status: FalseSlave Status: False [EXC 12:37:34.848] NullReferenceException: Object reference not set to an instance of an object CivilianPopulationRevamp.CivilianDockGrowth.FixedUpdate () [LOG 12:37:34.848] [CivilianPop Revamp]Starting FixedUpdate! [LOG 12:37:34.848] [CivilianPop Revamp]Master Status: TrueSlave Status: False [LOG 12:37:34.848] [CivilianPop Revamp]Finished FixedUpdate! [LOG 12:37:34.848] [CivilianPop Revamp]Starting FixedUpdate! [LOG 12:37:34.848] [CivilianPop Revamp]Master Status: FalseSlave Status: True [LOG 12:37:34.848] [CivilianPop Revamp]Finished FixedUpdate! [LOG 12:37:34.850] [CivilianPop Revamp]Starting FixedUpdate! [LOG 12:37:34.850] [CivilianPop Revamp]Master Status: FalseSlave Status: False [EXC 12:37:34.850] NullReferenceException: Object reference not set to an instance of an object CivilianPopulationRevamp.CivilianDockGrowth.FixedUpdate () The above is a sample of two update cycles. As can be seen, OnFixedUpdate runs as expected: It starts for the master, prints the master/slave status, and ends. This is repeated twice. FixedUpdate, however, results in three different instances of updates. The first two are expected (once for master, once for slave). But the last (which has both master and slave as false) results in a NullReferenceException. I've run a few tests and it looks like the problem lies in the line with List<CivilianDockGrowth> listOfCivilianParts = vessel.FindPartModulesImplementing<CivilianDockGrowth> (); I can patch that up easily, but I think the root of the problem is that there is a third method being called during update. What's really confusing me is that it seems to have both master and slave as false...which means that the part would not be present during the OnStart method (which successfully executed) Here is my code. My module being called is CivilianDockGrowth, which inherits CivilianPopulationRegulator. Everything within the if-statement can be ignored but I've included it just in case it proves useful. using System; using System.Collections.Generic; using UnityEngine; using KSP; namespace CivilianPopulationRevamp { [KSPAddon (KSPAddon.Startup.Flight, false)] public class CivilianDockGrowth : CivilianPopulationRegulator { public override void OnStart (StartState state) { bool shouldCheckForUpdate = getCheckForUpdate (); if (shouldCheckForUpdate) { //if master/slaves not set, flight status...should only be run once Debug.Log (debuggingClass.modName + this.name + " is running OnStart()!"); List<CivilianDockGrowth> partsWithCivies = vessel.FindPartModulesImplementing<CivilianDockGrowth> (); foreach (CivilianDockGrowth part in partsWithCivies) {//reset all master/slaves part.master = false; part.slave = true; } //assign this part as master master = true; slave = false; } else { //if master/slave set or flight status fail. Should be run n-1 times where n = #parts Debug.Log (debuggingClass.modName + "WARNING: " + this.name + " is skipping OnStart!"); } } public void FixedUpdate () { if (!HighLogic.LoadedSceneIsFlight) return; Debug.Log (debuggingClass.modName + "Starting FixedUpdate!"); //if (!master & !slave) //return; int civilianPopulation = 0; int nonCivilianPopulation = 0; int civilianPopulationSeats = 0; double percentCurrentCivilian = 0d; Debug.Log (debuggingClass.modName + "Master Status: " + master + "Slave Status: " + slave); List<CivilianDockGrowth> listOfCivilianParts = vessel.FindPartModulesImplementing<CivilianDockGrowth> (); if (master == true) { //master is set during OnStart() double dt = GetDeltaTimex (); //Section to calculate growth variables civilianPopulation = countCiviliansOnShip (listOfCivilianParts);//number of seats taken by civilians in parts using class nonCivilianPopulation = countNonCiviliansOnShip (listOfCivilianParts);//number of civilianPopulationSeats = countCivilianSeatsOnShip (listOfCivilianParts);//total seats implementing class percentCurrentCivilian = getResourceBudget (debuggingClass.civilianResource);//get current value of Civilian Counter (0.0-1.0) percentCurrentCivilianRate = calculateLinearGrowthRate () * getRecruitmentSoIModifier (); //how much civilianCounter will change on iteration if (HighLogic.CurrentGame.Mode == Game.Modes.CAREER) getTaxes (civilianPopulation, dt); //Section to create Civilians part.RequestResource (debuggingClass.civilianResource, -percentCurrentCivilianRate * dt); if ((percentCurrentCivilian > 1.0) && (civilianPopulationSeats > civilianPopulation + nonCivilianPopulation)) { placeNewCivilian (listOfCivilianParts); part.RequestResource (debuggingClass.civilianResource, 1.0); }//end if condition to create Civilians } Debug.Log (debuggingClass.modName + "Finished FixedUpdate!"); } // end FixedUpdate /// <summary> /// Calculates the growth rate for civilians taking rides up to the station. /// TODO: /// </summary> /// <returns>The linear growth rate.</returns> public double calculateLinearGrowthRate () { double myRate = 0d;//seems to be essential to create a middle variable, else rate does not update (returns to 0) myRate = populationGrowthModifier; return myRate; } /// <summary> /// Gets the recruitment modifier from being with Kerbin's, Mun's. or Minmus' SoI. It is easier for competing /// programs to get astronauts into Kerbin orbit than it is to Mun/Minus. None of them are as good as you are. /// </summary> /// <returns>The recruitment modifier due to.</returns> double getRecruitmentSoIModifier () { if (!vessel.LandedOrSplashed) { ////print(FlightGlobals.currentMainBody.name); double recruitmentRateModifier = 0d; //if(vessel.situation.ToString == "Orbit") switch (FlightGlobals.currentMainBody.name) { case "Kerbin": //Debug.Log (debuggingClass.modName + "Currently near Kerbin!"); recruitmentRateModifier = 1.0; return recruitmentRateModifier; case "Mun": //Debug.Log (debuggingClass.modName + "Currently near Mun!"); recruitmentRateModifier = 0.5; return recruitmentRateModifier; case "Minmus": //Debug.Log (debuggingClass.modName + "Currently near Minmus!"); recruitmentRateModifier = 0.25; return recruitmentRateModifier; default: //Debug.Log (debuggingClass.modName + "I don't care where I am!"); recruitmentRateModifier = 0; return recruitmentRateModifier; } } //Debug.Log (debuggingClass.modName + "I'm landed!"); return 0;//else case } } } Which inherits: using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; using KSP; namespace CivilianPopulationRevamp { //[KSPAddon (KSPAddon.Startup.Flight, false)] public class CivilianPopulationRegulator : BaseConverter { /// <summary> /// The current rate at which a civilian is created. Typically around 1E-8 to start. /// </summary> [KSPField (isPersistant = true, guiActive = true, guiName = "Current Growth Rate")] public double percentCurrentCivilianRate = 0d; [KSPField (isPersistant = true, guiActive = false)] public double populationGrowthModifier; /// <summary> /// The time until taxes; once each day /// </summary> [KSPField (isPersistant = true, guiActive = true, guiName = "Time until Rent payment")] public double TimeUntilTaxes = 21600.0; /// <summary> /// The last time since calculateRateOverTime() was run. Used to calculate time steps (?) /// </summary> [KSPField (isPersistant = true, guiActive = false)] public float lastTime; //only one part with this can be the master on any vessel. //this prevents duplicating the population calculation public bool master = false; public bool slave = false; /// <summary> /// Gets the first part within the vessel implementing Civilian Population and assigns it as the master. Also /// sets all other parts implementing Civilian Population as slaves. /// </summary> /// <returns>The master part.</returns> public growthRate getMaster<growthRate> (List<growthRate> partsWithCivies) where growthRate: CivilianPopulationRegulator { growthRate foundMaster = null; foreach (growthRate p in partsWithCivies) { if (p.master) { //initially only executes if master is set in OnStart() if (foundMaster != null) { //if this is NOT the first time executing; seems to never execute p.slave = true; p.master = false; Debug.Log (debuggingClass.modName + "Master part found; set to slave"); } else { foundMaster = p; Debug.Log (debuggingClass.modName + "Master part set"); } } } return foundMaster;//first part containing Civilian Population resource } /// <summary> /// Checks status of on scene, vessel, and pre-initiliazation of craft. /// </summary> /// <returns><c>true </c>, if active flight and no master/slave detected in part, <c>false</c> otherwise.</returns> public bool getCheckForUpdate () { if (!HighLogic.LoadedSceneIsFlight) {//only care about running on flights because master/slaves are being set once return false; } if (this.vessel == null) { //Make sure vessel is not empty (likely will cause error) return false; } if (master || slave) { //If (for whatever reason) master/slaves already assigned (such as previous flight) return false; } return true; } /// <summary> /// Counts Civilians within parts implementing CivilianPopulationRegulator class. This should be limited to only /// Civilian Population Parts. It also only counts Kerbals with Civilian Population trait. Iterates first over each /// part implementing CivilianPopulationRegulator, and then iterates over each crew member within that part. /// </summary> /// <returns>The number of Civilians on the ship</returns> /// <param name="listOfMembers">List of members.</param> public int countCiviliansOnShip<growthRate> (List<growthRate> listOfMembers) where growthRate: CivilianPopulationRegulator //to get current ship, use this.vessel.protoVessel { int numberCivilians = 0; foreach (growthRate myRegulator in listOfMembers) {//check for each part implementing CivilianPopulationRegulator if (myRegulator.part.protoModuleCrew.Count > 0) { foreach (ProtoCrewMember kerbalCrewMember in myRegulator.part.protoModuleCrew) {//check for each crew member within each part above if (kerbalCrewMember.trait == debuggingClass.civilianTrait) { numberCivilians++; }//end if civilian }//end foreach kerbalCrewMember }//end if crew capacity }//end foreach part implementing class return numberCivilians;//number of Kerbals with trait: debuggingClass.civilianTrait -> Civilian } public int countNonCiviliansOnShip<growthRate> (List<growthRate> listOfMembers) where growthRate: CivilianPopulationRegulator { int numberNonCivilians = 0; foreach (growthRate myRegulator in listOfMembers) {//check for each part implementing CivilianPopulationRegulator if (myRegulator.part.protoModuleCrew.Count > 0) { foreach (ProtoCrewMember kerbalCrewMember in myRegulator.part.protoModuleCrew) {//check for each crew member within each part above if (kerbalCrewMember.trait != debuggingClass.civilianTrait) { numberNonCivilians++; }//end if nonCivilian }//end foreach kerbalCrewMember }//end if crew capacity }//end foreach part implementing class return numberNonCivilians;//number of Kerbals without trait: debuggingClass.civilianTrait -> Civilian } /// <summary> /// Counts the civilian seats on ship. /// </summary> /// <returns>The civilian seats on ship.</returns> /// <param name="listOfMembers">List of members.</param> public int countCivilianSeatsOnShip<growthRate> (List<growthRate> listOfMembers) where growthRate: CivilianPopulationRegulator { int numberPossibleSeats = 0; foreach (growthRate myRegulator in listOfMembers) { numberPossibleSeats += myRegulator.part.CrewCapacity; } return numberPossibleSeats; } /// <summary> /// Calculates the rent based on the number of Kerbals within a ship. /// TODO: Implement changing values after mod successfully altered /// </summary> /// <returns>The total rent.</returns> /// <param name="numberOfCivilians">Number of civilians.</param> int calculateRent (int numberOfCivilians) { int rentRate = 200; int totalRent = 0; totalRent = numberOfCivilians * rentRate;//Use fixed value for testing return totalRent; } /// <summary> /// Gets the highest module growth rate of all modules on the craft. Growth rates come from the part's .cfg files. /// </summary> /// <returns>The highest module growth rate.</returns> /// <param name="listOfMembers">List of members.</param> public double getHighestModuleGrowthRate<growthRate> (List<growthRate> listOfMembers) where growthRate: CivilianPopulationRegulator { double exponentialRate = 0d;//the malthusian parameter used to calculate the population growth. Taken as largest value on vessel. foreach (CivilianPopulationRegulator myRegulator in listOfMembers) { if (myRegulator.populationGrowthModifier > exponentialRate) { exponentialRate = myRegulator.populationGrowthModifier; } } return exponentialRate;//returns the largest rate among parts using CivilianPopulationRegulator class. } /// <summary> /// This method will place a new civilian in a part containing CivlianPopulationRegulator. It should only /// be called when there are seat positions open in onesuch part. Perhaps in the future, there will be a specific /// part that generates Civilians. /// </summary> /// <param name="listOfMembers">List of members.</param> public void placeNewCivilian<growthRate> (List<growthRate> listOfMembers) where growthRate : CivilianPopulationRegulator { ProtoCrewMember newCivilian = createNewCrewMember (debuggingClass.civilianTrait); bool civPlaced = false; foreach (growthRate currentPart in listOfMembers) { if (currentPart.part.CrewCapacity > currentPart.part.protoModuleCrew.Count () && !civPlaced) { if (currentPart.part.AddCrewmember (newCivilian)) { Debug.Log (debuggingClass.modName + newCivilian.name + " has been placed successfully by placeNewCivilian"); civPlaced = true; } } } if (civPlaced == false) Debug.Log (debuggingClass.modName + "ERROR: " + newCivilian.name + " could not be placed in method placeNewCivilian"); } /// <summary> /// Creates the new crew member of trait kerbalTraitName. It must be of type Crew because they seem to be the only /// type of Kerbal that can keep a trait. /// </summary> /// <returns>The new crew member.</returns> /// <param name="kerbalTraitName">Kerbal trait name.</param> ProtoCrewMember createNewCrewMember (string kerbalTraitName) { KerbalRoster roster = HighLogic.CurrentGame.CrewRoster; ProtoCrewMember newKerbal = roster.GetNewKerbal (ProtoCrewMember.KerbalType.Crew); KerbalRoster.SetExperienceTrait (newKerbal, kerbalTraitName);//Set the Kerbal as the specified role (kerbalTraitName) Debug.Log (debuggingClass.modName + "Created " + newKerbal.name + ", a " + newKerbal.trait); return newKerbal;//returns newly-generated Kerbal } /// <summary> /// Gets the delta time of the physics (?) update. First it confirms the game is in a valid state. Then it calculats /// the time between physics update by comparing with Planetarium.GetUniversalTime() and GetMaxDeltaTime(). /// </summary> /// <returns>The delta time.</returns> protected double GetDeltaTimex () { if (Time.timeSinceLevelLoad < 1.0f || !FlightGlobals.ready) { //Error: Not sure what this error is for...maybe not enough time since load? Debug.Log(debuggingClass.modName + "ERROR: check timeSinceLevelLoad/FlightGlobals"); Debug.Log(debuggingClass.modName + "timeSinceLevelLoad = " + Time.timeSinceLevelLoad); Debug.Log(debuggingClass.modName + "FlightGlobals.ready = " + !FlightGlobals.ready); return -1; } if (Math.Abs (lastUpdateTime) < float.Epsilon) { //Error: Just started running Debug.Log(debuggingClass.modName + "ERROR: check lastUpdateTime"); Debug.Log(debuggingClass.modName + "lastUpdateTime = " + lastUpdateTime); lastUpdateTime = Planetarium.GetUniversalTime(); return -1; } var deltaTime = Math.Min (Planetarium.GetUniversalTime () - lastUpdateTime, ResourceUtilities.GetMaxDeltaTime ()); return deltaTime; //why is deltaTime == 0? //return deltaTime; } public void getTaxes (int numCivilians, double reduceTime) { int rentAcquired = 0; TimeUntilTaxes -= reduceTime; if (TimeUntilTaxes <= 0) { rentAcquired = calculateRent (numCivilians); Funding.Instance.AddFunds (rentAcquired, TransactionReasons.Vessels); TimeUntilTaxes = 21600; } } /// <summary> /// Looks over vessel to find amount of a given resource matching name. In this project's scope, it is used /// in order to determine how far along the civilian growth counter is towards creating a new Kerbal. /// </summary> /// <returns>The amount of resource matching name.</returns> /// <param name="name">Name.</param> public double getResourceBudget (string name) { if (this.vessel != null) { var resources = vessel.GetActiveResources (); for (int i = 0; i < resources.Count; i++) { if (resources [i].info.name == name) { return (double)resources [i].amount; } } } return 0; } //Anything below this, I don't know what it does but it is essential to keep from seeing //"No Resource definition found for RESOURCE" error message in OnFixedUpdate. [KSPField] public string RecipeInputs = ""; [KSPField] public string RecipeOutputs = ""; [KSPField] public string RequiredResources = ""; public ConversionRecipe Recipe { get { return _recipe ?? (_recipe = LoadRecipe ()); } } private ConversionRecipe _recipe; protected override ConversionRecipe PrepareRecipe (double deltatime) { if (_recipe == null) _recipe = LoadRecipe (); UpdateConverterStatus (); if (!IsActivated) return null; return _recipe; } private ConversionRecipe LoadRecipe () { var r = new ConversionRecipe (); try { if (!String.IsNullOrEmpty (RecipeInputs)) { var inputs = RecipeInputs.Split (','); for (int ip = 0; ip < inputs.Count (); ip += 2) { print (String.Format ("[REGOLITH] - INPUT {0} {1}", inputs [ip], inputs [ip + 1])); r.Inputs.Add (new ResourceRatio { ResourceName = inputs [ip].Trim (), Ratio = Convert.ToDouble (inputs [ip + 1].Trim ()) }); } } if (!String.IsNullOrEmpty (RecipeOutputs)) { var outputs = RecipeOutputs.Split (','); for (int op = 0; op < outputs.Count (); op += 3) { print (String.Format ("[REGOLITH] - OUTPUTS {0} {1} {2}", outputs [op], outputs [op + 1], outputs [op + 2])); r.Outputs.Add (new ResourceRatio { ResourceName = outputs [op].Trim (), Ratio = Convert.ToDouble (outputs [op + 1].Trim ()), DumpExcess = Convert.ToBoolean (outputs [op + 2].Trim ()) }); } } if (!String.IsNullOrEmpty (RequiredResources)) { var requirements = RequiredResources.Split (','); for (int rr = 0; rr < requirements.Count (); rr += 2) { print (String.Format ("[REGOLITH] - REQUIREMENTS {0} {1}", requirements [rr], requirements [rr + 1])); r.Requirements.Add (new ResourceRatio { ResourceName = requirements [rr].Trim (), Ratio = Convert.ToDouble (requirements [rr + 1].Trim ()), }); } } } catch (Exception) { print (String.Format ("[REGOLITH] Error performing conversion for '{0}' - '{1}' - '{2}'", RecipeInputs, RecipeOutputs, RequiredResources)); } return r; } } } Has anyone ever seen anything like this before?