1. The forum will be closing soon permanently. Please read the announcement here

    Note: User registration has been closed. We do not accept any new accounts.

How to Initialize Your Mod

Discussion in 'Modding' started by Levitator, Dec 1, 2015.

Thread Status:
This last post in this thread was made more than 31 days old.
  1. Levitator

    Levitator Apprentice Engineer

    Messages:
    274
    If you are like me, you will look to MySessionComponentBase.BeforeStart() and MyGameLogicComponent.Init() as places to initialize your mod state and, although this is a good place to initialize your internal state variables, there is an inconsistent and exasperating problem that will crop up if you attempt to do pretty much anything in MyAPIGateway from inside these methods. You will get "World is corrupted and could not be loaded" because MyAPIGateway is not initialized yet and attempting to use it will throw exceptions, probably of the null reference variety. The best solution that I could find looks like this.

    Code:
     
    /*
    * Levitator's Space Engineers Mod-Creation Class
    *
    * This mostly just serves to abstract away some initialization problems
    * with ModAPIGateway
    *
    * Reuse is free as long as you attribute the author.
    *
    * V1.01
    *
    */
     
    using Sandbox.Common;
     
    namespace Levitator
    {
    	//The attribute is not inheritable, so add it to your subclass
    	//[MySessionComponentDescriptor(MyUpdateOrder.X)]
    	public abstract class ModBase : MySessionComponentBase
    	{
    		protected bool Initialized;
    	
    		//Implement these to simplify initialization, shutdown, loading, and the priodic updates
    		protected abstract bool Initialize(); //Return true for success, false to try again on next update
    		protected abstract void Shutdown(); //Called from UnloadData()
    		protected abstract void Update(); //Called for each update of whatever kind you specified in the attribute
    		protected abstract void LoadDataDefinitely(); //Only called after we are initialized
     
    		//
    		//Viscera
    		//
    		public void DoPolling()
    		{
    			if (!Initialized)
    			{
    				Initialized = Initialize();
    				if (Initialized) LoadDataDefinitely();
    			}
    			if(Initialized)
    				Update();
    		}
     
    		public override void LoadData()
    		{
    			if (Initialized)
    				LoadDataDefinitely();
    			base.LoadData();
    		}
     
    		protected override void UnloadData()
    		{
    			Shutdown();
    			base.UnloadData();
    		}
     
    		public override void UpdateBeforeSimulation()
    		{
    			DoPolling();
    			base.UpdateBeforeSimulation();
    		}
    		
    		public override void Simulate()
    		{
    			DoPolling();
    			base.Simulate();
    		}
    		public override void UpdateAfterSimulation()
    		{
    			DoPolling();
    			base.UpdateAfterSimulation();
    		}
    	}
    }
     
    
    If anyone knows a better way, I would be interested to hear it. UpdateBeforeSimulation() is probably interchangeable with whichever update style you choose by way of the MySessionComponentDescriptor. A related issue is when to perform cleanup. I am currently guessing that MySessionComponentBase.UpdatingStopped() and MyGameLogicComponent.OnRemovedFromScene() are the places to do it. In particular, if you do not detach your event handlers, you may leak memory.

    Levitator

    P.S. Regarding MySessionComponentBase cleanup: midspace seems to do his cleanup and handler detachment in UnloadData(), and he seems to know what he's doing, so this is probably the place to do it.
     
    Last edited: Dec 7, 2015
  2. Draygo

    Draygo Senior Engineer

    Messages:
    1,297
    check if session is null

    Code:
    if (MyAPIGateway.Session == null)
    return;
    
    Do that in the update before simulation then init when it isn't null. That's the way I see most people initializing right now because of the issues with Gateway.
     
  3. Levitator

    Levitator Apprentice Engineer

    Messages:
    274
    The problem is not limited to operations on Session. Pretty much any call to MyAPIGateway will throw. So far, I'm finding that the gateway is always initialized by the time Simulation or Update is ready, which makes sense logically. The other entry points may be called before MyAPIGateway is ready, but it seems that the Simulate and Update methods will not. The only thing that remains is to throw in a conditional to make sure you don't initialize twice.

    Levitator
     
  4. Digi

    Digi Senior Engineer

    Messages:
    2,393
    I believe that MyAPIGateway.Session or MyAPIGateway.Session.Player can be null on DS when updates are run, which is why people just check it to be sure.
     
    • Informative Informative x 1
  5. Levitator

    Levitator Apprentice Engineer

    Messages:
    274
    This is very strange. What is there to update or simulate without an active session? A helpful observation nonetheless.

    Levitator
     
  6. Digi

    Digi Senior Engineer

    Messages:
    2,393
    Well it's not always null on DS, but it can be at the start when the updates are run.
     
  7. Levitator

    Levitator Apprentice Engineer

    Messages:
    274
    Updated the original post with new code. This is a base class where, if you implement the abstract methods, you get a simplified interface to MySessionComponent where the initialization and loading methods are called at the appropriate times. You may still need to implement your Initialize() method so as to avoid timing pitfalls and return false if initialization is not possible. This forces a retry on the next update.

    Levitator
     
  8. Digi

    Digi Senior Engineer

    Messages:
    2,393
    Have you tested if LoadDataDefinitely(); actually ever triggers ? I doubt that LoadData() executes after updates started.

    But I suggest keeping it simple:
    Code:
    namespace YourNameSpace
    {
    	[MySessionComponentDescriptor(MyUpdateOrder.AfterSimulation)]
    	public class WhateverClass : MySessionComponentBase
    	{
    		public bool init { get; private set; }
    		
    		public void Init()
    		{
    			init = true;
    			
    			// Register stuff
    		}
    		
    		protected override void UnloadData()
    		{
    			init = false;
    			
    			// Unregister stuff if you have any
    		}
    		
    		public override void UpdateAfterSimulation()
    		{
    			if(!init)
    			{
    				if(MyAPIGateway.Session == null)
    					return;
    				
    				Init();
    			}
    			
    			// Do stuff every tick
    			// I strongly suggest skipping some ticks if your code doesn't need to be that accurate
    		}
    		
    		// You can override other methods, for example SaveData() which is called when the game is saved
    	}
    }
    AfterSimulation is good for most cases, unless they know what they're doing and need the code before simulation.
     
  9. Levitator

    Levitator Apprentice Engineer

    Messages:
    274
    I don't know if LoadData() is ever called after simulation has started, but if it doesn't, then it is an unusable method because I have only seen it triggered before the file IO methods are initialized in MyAPIGateway. You will want to do your data loading inside of Initialize(). I don't know under what circumstances the engine would want you to resync your state from disk, but the intent behind these interfaces and the object lifetimes are not really documented. Presumably, it's possible, or they would not have implemented a LoadData() method. At any rate, your code seems to do mostly what mine does, except you check Session. Also, I abstract the three types of update into one method since it doesn't make sense to have three kinds of updates when only one can be enabled at a time, to my knowledge.

    Levitator
     
  10. Digi

    Digi Senior Engineer

    Messages:
    2,393
    You should pick a single call for LoadDataDefinitely() because if that triggers twice for someone that hooks a message event there it'll be a multiplied message event mess, especially if they reload the world.
     
  11. Levitator

    Levitator Apprentice Engineer

    Messages:
    274
    In theory, the game could create your SessionComponent once and then try to reuse it, in which case LoadData() would get called twice. I'm not seeing it happen, but then why does LoadData() even exist?

    Levitator
     
  12. Gwindalmir

    Gwindalmir Senior Engineer

    Messages:
    1,006
    Here's what I do:

    For most blocks, in Init():
    Code:
    			Entity.NeedsUpdate |= MyEntityUpdateEnum.BEFORE_NEXT_FRAME;
    
    Then perform all my init code in:
    Code:
    		public override void UpdateOnceBeforeFrame()
    		{
    			if (MyAPIGateway.Multiplayer.IsServer)
    				Entity.NeedsUpdate |= MyEntityUpdateEnum.EACH_10TH_FRAME;
    		}
    
    It has the advantage that it appears to be called before the first frame (it can be explicitly called during runtime as well, each time only triggers once, watch out for that).

    Disadvantage is not all blocks support it (Oxygen Generator being one).

    For Session, sadly it seems you must use an MyUpdateOrder if you want to be guaranteed to init properly.
    Which means after init, it just wastes cycles calling an empty method over and over if that's all you needed.

    However I am able to use LoadData/UnloadData for most of what I do.
     
    Last edited: Dec 14, 2015
Thread Status:
This last post in this thread was made more than 31 days old.