Welcome to Keen Software House Forums! Log in or Sign up to interact with the KSH community.
  1. You are currently browsing our forum as a guest. Create your own forum account to access all forum functionality.

Airlock script to complex

Discussion in 'Programming Questions and Suggestions' started by Martin R Wolfe, Feb 25, 2017.

Thread Status:
This last post in this thread was made more than 31 days old.
  1. Martin R Wolfe Trainee Engineer

    Messages:
    80
    This code worked fine until I added the code to serialize so I could use storage to save the state of the operation of the airlocks and have them restored on loading the save game.

    Now when I run the script with the command to pressurise an airlock ("Pressurise: Hangar;" in the run box ) it returns a To Complex exception. The script still loads and responds to "TimerPing:;" until this happens. Any ideas what I have missed in trimming the script down? As can be seen from my comments in the code I still have a small bit to ad to finnish the script but it should run as is.

    Code:
    //Configuration constants
    private const double StartDelay=4;
    //Private variables
    private DateTime SystemLoadTime;
    private List<Airlock> Airlocks;
    private List<string> saExternalPressureSensors;
    private List<IMyAirVent>ExternalPressureSensors;
    public Program() {
        SystemLoadTime=DateTime.Now;
        Initialise(Storage);
    }
    public void Save() {
        Storage=Serialize().ToString().Trim();
    }
    public void Main(string argument) {
        //Delay by StartDelay to give save game time to fully load
        if((DateTime.Now-SystemLoadTime).TotalSeconds<StartDelay) return; 
        //Extract the commands from the arguments
        List<ParameterWithValue> CommandLines=ParametersWithValuesFromString(argument);
        //Load the blocks to the classes
        foreach(Airlock anAirlock in Airlocks) anAirlock.LoadBlocks(GridTerminalSystem, Me.CubeGrid);
        List<IMyAirVent> FoundVents = new List<IMyAirVent>();
        foreach(string aSensor in saExternalPressureSensors){
            GridTerminalSystem.GetBlocksOfType<IMyAirVent>(FoundVents,
                x=>((x.CustomName==aSensor)&&(x.CubeGrid==Me.CubeGrid)));
            ExternalPressureSensors.AddRange(FoundVents);
        }
        //Process the commands
        foreach(ParameterWithValue aCommand in CommandLines)
            switch(aCommand.Name){
            case "REBOOT":  //REBOOT command must be the only argument with no params
                if((CommandLines.Count==1) & (aCommand.Values.Count==0)){
                    Echo(Me.CustomName + ": REBOOTING");
                    Initialise(Me.CustomData);
                    }
                break;
            case "TimerPing":
                Echo(Me.CustomName + ": Status Ok");
                Echo("External pressure: " + 
                    (ExternalPressureSensors.Count>0
                        ?ExternalPressureSensors[0].GetOxygenLevel().ToString("0.00%")
                        :"Sensor Missing")); 
                Echo("Airlock count: " + Airlocks.Count);
                foreach(Airlock aAirlock in Airlocks){
                    Echo("Airlock: " + aAirlock.ToString());
                }
    Echo(Serialize().ToString()); 
                break;
            case "Pressurise":
                foreach(string AirlockName in aCommand.Values)
                    foreach(Airlock anAirlock in Airlocks.FindAll(x=>(x.Name==AirlockName)))
                        anAirlock.Pressurise(1,  true);
                break;
            case "Depressurise": 
                foreach(string AirlockName in aCommand.Values) 
                    foreach(Airlock anAirlock in Airlocks.FindAll(x=>(x.Name==AirlockName))) 
                        anAirlock.Pressurise(ExternalPressure,  false); 
                break; 
            default:
                Echo(Me.CustomName + ": Error unrecognised command: " + aCommand.Name);
                break;
            }
        //Free the blocks from the classes 
        foreach(Airlock anAirlock in Airlocks) anAirlock.ReleaseBlocks();
        ExternalPressureSensors.Clear(); 
    }
    private void Initialise(string ConfigString){
        //Extract the configuration lines  from ConfigString 
        List<ParameterWithValue> ParameterLines=ParametersWithValuesFromString(ConfigString); 
        //Reset clases
        Airlocks=new List<Airlock>();
        saExternalPressureSensors=new List<string>(); 
        ExternalPressureSensors=new List<IMyAirVent>();
        //Initialise classes to config
        foreach(ParameterWithValue aParameter in ParameterLines) 
            switch(aParameter.Name){ 
            case "Airlock": 
                Airlocks.Add(new Airlock(aParameter.Values)); 
                break;
            case "ExternalPressureSensor":
                saExternalPressureSensors.AddRange(aParameter.Values);
                break;
            default: 
                break; 
            } 
    }
    private StringBuilder Serialize(){
        StringBuilder Result=new StringBuilder();
        foreach(string sSensor in saExternalPressureSensors)
            Result.AppendLine().AppendFormat("ExternalPressureSensor: {0};", sSensor);
        foreach(Airlock aAirlock in Airlocks) Result.AppendLine().Append(aAirlock.Serialize());
        return Result;
    }
    public struct ParameterWithValue { 
        public string Name; 
        public List<string> Values;
        public ParameterWithValue(string aCommandLine){
            List<string> CommandWithParams = new List<string>(aCommandLine.Split(new char[] {':'},2));
            if(CommandWithParams.Count<2) CommandWithParams.Add("");
            Name=CommandWithParams[0];
            Values=new List<string>();
            foreach(string aParameter in (new List<string>(CommandWithParams[1].Trim().Split(',')))){
                if(aParameter.Trim()!="") Values.Add(aParameter.Trim());
            }
        }
    } 
    static public  List<ParameterWithValue> ParametersWithValuesFromString(string ParameterString){
        //Extract the commands from the arguments 
        List<ParameterWithValue> ParametersWithValues=new List<ParameterWithValue>(); 
        foreach(string aCommandLine in (new List<string>(ParameterString.Trim().Split(';')))) 
            if(aCommandLine.Trim()!="") ParametersWithValues.Add(new ParameterWithValue(aCommandLine.Trim())); 
        return ParametersWithValues;
    }
    public double ExternalPressure =>ExternalPressureSensors.Count>0 
                        ?ExternalPressureSensors[0].GetOxygenLevel():0;
    private class Airlock{
        private const string SupplySuffix="Supply Vent"; 
        private const string StorageSuffix="Storage Vent";
        public enum enumBlockStatus {
            BlocksNotLoaded,
            BlocksLoaded
        }
        public string Name{get; private set;}
        private List<string> _saInnerDoors;
        private List<string> _saOuterDoors; 
        private List<string> _saInnerBlastDoors; 
        private List<string> _saOuterBlastDoors; 
        private List<string> _saStorageTanks; 
        public enumBlockStatus BlockStatus{get; private set;}
        private List<IMyAirVent> _SupplyVents;
        private List<IMyAirVent> _StorageVents;
        private List<IMyDoor> _InnerDoors; 
        private List<IMyDoor> _OuterDoors; 
        private List<BlastDoor> _InnerBlastDoors;   
        private List<BlastDoor> _OuterBlastDoors; 
        private List<IMyGasTank> _StorageTanks;   
        private IEnumerator<AirlockStatus> PressuriseProg; 
        public Airlock(List<string> ConfigValues,AirlockStatus status=new AirlockStatus()){
            int i=0;
            if(ConfigValues.Count>i) Name=ConfigValues[i];
            else Name="";
            i++;
            _saInnerDoors=new List<string>(); 
            _saOuterDoors=new List<string>(); 
            _saInnerBlastDoors=new List<string>(); 
            _saOuterBlastDoors=new List<string>();
            _saStorageTanks=new List<string>();
            BlockStatus=enumBlockStatus.BlocksNotLoaded;
            _SupplyVents=new List<IMyAirVent>(); 
            _StorageVents=new List<IMyAirVent>();
            _InnerDoors=new List<IMyDoor>(); 
            _OuterDoors=new List<IMyDoor>(); 
            _InnerBlastDoors=new List<BlastDoor>();   
            _OuterBlastDoors=new List<BlastDoor>();
            _StorageTanks=new List<IMyGasTank>(); 
            PressuriseProg=null;
            while(ConfigValues.Count>i){
                List<string> ConfigValue = new List<string>(ConfigValues[i].Trim().Split(new char[]{':'},2));
                if(ConfigValue.Count>1){
                switch(ConfigValue[0].Trim()){
                    case "InnerDoor":
                        _saInnerDoors.Add(ConfigValue[1].Trim());
                        break;
                    case "OuterDoor": 
                        _saOuterDoors.Add(ConfigValue[1].Trim()); 
                        break; 
                    case "InnerBlastDoor": 
                        _InnerBlastDoors.Add(new BlastDoor(ConfigValue[1].Trim()));   
                        break; 
                    case "OuterBlastDoor": 
                        _OuterBlastDoors.Add(new BlastDoor(ConfigValue[1].Trim())); 
                        break; 
                    case "StorageTank":
                        _saStorageTanks.Add(ConfigValue[1].Trim());
                        break;
                    case "AirlockStatus":
                        AirlockStatus LoadedStatus=AirlockStatus.Deserialize(ConfigValue[1].Trim());
                        //if(LoadedStatus.RunState!=AirLockRunState.Idle)
                            //PressuriseProg=PressuriseProgram(LoadedStatus).GetEnumerator();
                        break;
                    default:
                        break;
                    }
                }
                i++;
            }
        }
        public StringBuilder Serialize(){
            StringBuilder Result = new StringBuilder("Airlock: ").Append(Name);
            foreach(string sDoor in _saInnerDoors) Result.AppendFormat(",\n\rInnerDoor: {0}", sDoor);
            foreach(string sDoor in _saOuterDoors) Result.AppendFormat(",\n\rOuterDoor: {0}", sDoor);
            foreach(BlastDoor aDoor in _InnerBlastDoors)
                Result.AppendFormat(",\n\rInnerBlastDoor: {0}", aDoor.Serialize());
            foreach(BlastDoor aDoor in _OuterBlastDoors) 
                Result.AppendFormat(",\n\rOuterBlastDoor: {0}", aDoor.Serialize());
            foreach(string sTank in _saStorageTanks) Result.AppendFormat(",\n\rStorageTank: {0}",sTank);
            Result.AppendFormat(",\n\r{0};",Status.Serialize()); 
            return Result;
        }
     
        override public string ToString() => Name 
            + "\n\rStatus: " + Status.RunState 
            + "\n\rPressure: " + Pressure.ToString("0.00%") 
            +"\n\rInner Doors: " + DoorState() + (GetDoorsLocked()?" Locked":" Unlocked") 
            +"\n\rOuter Doors: " + DoorState(false) + (GetDoorsLocked(false)?" Locked":" Unlocked") 
            +(_InnerBlastDoors.Count>0? 
                ("\n\rInner Blast Doors: " + BlastDoorState() + (GetBlastDoorsLocked()?" Locked":" Unlocked")):"") 
            +(_OuterBlastDoors.Count>0? 
                ("\n\rOuter Blast Doors: " + BlastDoorState(false) + (GetBlastDoorsLocked(false)?" Locked":" Unlocked")):""); 
        public void LoadBlocks(IMyGridTerminalSystem GTS,IMyCubeGrid CubeGrid){
            GTS.GetBlocksOfType<IMyAirVent>(_SupplyVents,
                x=>((x.CustomName==(Name+" "+SupplySuffix))&&(x.CubeGrid==CubeGrid))); 
            GTS.GetBlocksOfType<IMyAirVent>(_StorageVents, 
                x=>((x.CustomName==(Name+" "+StorageSuffix))&&(x.CubeGrid==CubeGrid)));
            List<IMyDoor> FoundDoors=new List<IMyDoor>(); 
            foreach(string sDoorName in _saInnerDoors){
                GTS.GetBlocksOfType<IMyDoor>(FoundDoors, 
                    x=>((x.CustomName==sDoorName)&&(x.CubeGrid==CubeGrid)));
                _InnerDoors.AddRange(FoundDoors);}
            foreach(string sDoorName in _saOuterDoors){ 
                GTS.GetBlocksOfType<IMyDoor>(FoundDoors,   
                    x=>((x.CustomName==sDoorName)&&(x.CubeGrid==CubeGrid))); 
                _OuterDoors.AddRange(FoundDoors);} 
            List<IMyGasTank> FoundTanks = new List<IMyGasTank>();
            foreach(string sTankName in _saStorageTanks){
                GTS.GetBlocksOfType<IMyGasTank>(FoundTanks,
                    x=>((x.CustomName==sTankName)&&(x.CubeGrid==CubeGrid)));
                _StorageTanks.AddRange(FoundTanks);}
            foreach(BlastDoor aBlastDoor in _InnerBlastDoors) aBlastDoor.LoadBlocks(GTS, CubeGrid);
            foreach(BlastDoor aBlastDoor in _OuterBlastDoors) aBlastDoor.LoadBlocks(GTS, CubeGrid); 
            BlockStatus=enumBlockStatus.BlocksLoaded;   
        }
        public void ReleaseBlocks(){
            BlockStatus=enumBlockStatus.BlocksNotLoaded; 
            _SupplyVents.Clear(); 
            _StorageVents.Clear();
            _InnerDoors.Clear();   
            _OuterDoors.Clear(); 
            _StorageTanks.Clear();
            foreach(BlastDoor aBlastDoor in _InnerBlastDoors) aBlastDoor.ReleaseBlocks();     
            foreach(BlastDoor aBlastDoor in _OuterBlastDoors) aBlastDoor.ReleaseBlocks(); 
        } 
        public double Pressure {
            get {
                double Result=-1;
                if(BlockStatus==enumBlockStatus.BlocksLoaded){
                    List<IMyAirVent> Vents=new List<IMyAirVent>(_SupplyVents);
                    Vents.AddRange(_StorageVents);
                    if(Vents.Count>0) Result=Vents[0].GetOxygenLevel();
                }
                return Result;
            }
        }
       
        public double StorageTankCapacity {
            get{
                double _TankCapacity=1;
                if(_StorageTanks.Count>0){
                    _TankCapacity=0;
                    foreach(IMyGasTank aTank in _StorageTanks) _TankCapacity+=aTank.FilledRatio;
                    _TankCapacity/=_StorageTanks.Count;
                }
                return _TankCapacity;
            }
        }
        public DoorStatus DoorState(bool Inner=true){
            DoorStatus DoorState=DoorStatus.Closed;
            List<IMyDoor> Doors;
            if(Inner) Doors=_InnerDoors;
            else Doors=_OuterDoors;
            foreach(IMyDoor aDoor in Doors)  switch(aDoor.Status){
                case DoorStatus.Open:
                    if((DoorState!=DoorStatus.Opening)&&(DoorState!=DoorStatus.Closing)) DoorState=DoorStatus.Open;
                    break; 
                case DoorStatus.Closing:
                    if(DoorState==DoorStatus.Closed) DoorState=DoorStatus.Closing;
                    break; 
                case DoorStatus.Opening:
                    DoorState=DoorStatus.Opening; 
                    break; 
            }
            return DoorState;
        }
        public DoorStatus BlastDoorState(bool Inner=true){ 
            DoorStatus bDoorState=DoorStatus.Closed; 
            List<BlastDoor> Doors; 
            if(Inner) Doors=_InnerBlastDoors; 
            else Doors=_OuterBlastDoors; 
            foreach(BlastDoor aDoor in Doors)  switch(aDoor.Status){ 
                case DoorStatus.Open: 
                    if((bDoorState!=DoorStatus.Opening)&&(bDoorState!=DoorStatus.Closing)) bDoorState=DoorStatus.Open; 
                    break; 
                case DoorStatus.Closing: 
                    if(bDoorState==DoorStatus.Closed) bDoorState=DoorStatus.Closing; 
                    break; 
                case DoorStatus.Opening: 
                    bDoorState=DoorStatus.Opening;   
                    break;   
            } 
            if(Doors.Count==0) bDoorState=DoorState(Inner);
            return bDoorState; 
        } 
        public bool GetDoorsLocked(bool Inner=true) {
            List<IMyDoor> Doors;
            if(Inner) Doors=_InnerDoors; 
            else Doors=_OuterDoors;
            bool Locked=true;
            foreach(IMyDoor aDoor in Doors) if(aDoor.Enabled) Locked=false;
            return Locked;
        }
     
        public void SetDoorsLocked(bool bLocked, bool Inner=true) { 
            List<IMyDoor> Doors; 
            if(Inner) Doors=_InnerDoors;   
            else Doors=_OuterDoors; 
            foreach(IMyDoor aDoor in Doors) aDoor.Enabled=!bLocked; 
        }
        public bool GetBlastDoorsLocked(bool Inner=true) {
            bool _Locked=true; 
            List<BlastDoor> Doors; 
            if(Inner) Doors=_InnerBlastDoors; 
            else Doors=_OuterBlastDoors; 
            foreach(BlastDoor aBlastDoor in Doors) if(!aBlastDoor.Locked) _Locked=false;
            return _Locked;
        } 
        public void SetBlastDoorsLocked(bool bLocked, bool Inner=true) { 
            List<BlastDoor> Doors;   
            if(Inner) Doors=_InnerBlastDoors;   
            else Doors=_OuterBlastDoors;   
            foreach(BlastDoor aBlastDoor in Doors) aBlastDoor.Locked=bLocked; 
        }
        class BlastDoor{
            public string Name {get; private set;}="";
            public enumBlockStatus BlockStatus=enumBlockStatus.BlocksNotLoaded;
            private List<IMyMotorStator> _Rotors;
            private double CloseAngle=0;
            private double OpenAngle=0;
           
            public BlastDoor(string ConfigString){
                List<string> ConfigParams=new List<string>(ConfigString.Trim().Split(new char[]{'|'},3));
                if(ConfigParams.Count>0 )Name=ConfigParams[0].Trim();
                if(ConfigParams.Count>1 )
                    if(ConfigParams[1].Trim()[0]=='r') CloseAngle=double.Parse(ConfigParams[1].Trim().Substring(1).Trim());
                    else CloseAngle=Math.Round(MathHelper.ToRadians(float.Parse(ConfigParams[1].Trim())),3); 
                if(ConfigParams.Count>2 )
                    if(ConfigParams[2].Trim()[0]=='r') OpenAngle=double.Parse(ConfigParams[2].Trim().Substring(1).Trim()); 
                    else OpenAngle=Math.Round(MathHelper.ToRadians(float.Parse(ConfigParams[2].Trim())),3);
                _Rotors=new List<IMyMotorStator>(); 
            }
            public StringBuilder Serialize()=>new StringBuilder(Name)
                .AppendFormat("| r{0}| r{1}", CloseAngle, OpenAngle); 
            public void LoadBlocks(IMyGridTerminalSystem GTS, IMyCubeGrid CubeGrid){
                GTS.GetBlocksOfType<IMyMotorStator>(_Rotors,     
                    x=>((x.CustomName==Name)&(x.CubeGrid==CubeGrid))); 
                BlockStatus=enumBlockStatus.BlocksLoaded;   
            }
            public void ReleaseBlocks(){ 
                BlockStatus=enumBlockStatus.BlocksNotLoaded;   
                _Rotors.Clear();     
            }
           
            public DoorStatus Status{
                get{
                    if(Math.Sign(_Rotors[0].TargetVelocity)==Math.Sign(CloseAngle-OpenAngle)){
                        if(CloseAngle!=Math.Round(_Rotors[0].Angle,3)) return DoorStatus.Closing;
                        return DoorStatus.Closed; 
                    }else{
                        if(OpenAngle!=Math.Round(_Rotors[0].Angle,3)) return DoorStatus.Opening; 
                        return DoorStatus.Open; 
                    }
                }
            }
            public void Open(bool bOpen=true){
                if(_Rotors.Count>0){
                    float TargetVel=Math.Abs(_Rotors[0].TargetVelocity)*(Math.Sign(CloseAngle-OpenAngle));
                    if(bOpen) TargetVel= -TargetVel;
                    foreach(IMyMotorStator aRotor in _Rotors) aRotor.TargetVelocity=TargetVel;
                }
            }
            public bool Locked{
                get{
                    bool _Locked=true;
                    foreach(IMyMotorStator aRotor in _Rotors) if(!aRotor.SafetyLock) _Locked=false;
                    return _Locked;
                }
                set{ 
                    foreach(IMyMotorStator aRotor in _Rotors) aRotor.SafetyLock=value; 
                } 
            }
            override public string ToString()=>Name+"\n\rStatus: " + Status;
        }
        public enum AirLockRunState { 
            ClosingDoors, 
            Pressurising, 
            Depressurising,
            Unlocking,
            Done, 
            Idle 
        } 
        public void RunAirlock(){
            if((PressuriseProg != null)
                && ((!PressuriseProg.MoveNext())
                        ||(PressuriseProg.Current.RunState==AirLockRunState.Done)))
                PressuriseProg = null;   
        }
        public AirlockStatus Status{
            get{
                RunAirlock();
                return (PressuriseProg == null) ? 
                    (new AirlockStatus()) : PressuriseProg.Current;
            }
        }
        public struct AirlockStatus{
            public double TargetPressure;
            public bool bPressurise;
            public AirLockRunState RunState;
            public AirlockStatus( 
                double targetPressure=1, 
                bool BPressurise=true, 
                AirLockRunState runState=AirLockRunState.Idle)
            {
                TargetPressure=targetPressure;
                bPressurise=BPressurise;
                RunState=runState;
            }
            public override string ToString()=>TargetPressure.ToString()+","+bPressurise+","+RunState;
            public StringBuilder Serialize() =>new StringBuilder()
                .AppendFormat("AirlockStatus: {0}, {1}, {2}", TargetPressure, bPressurise, RunState);
            static public AirlockStatus Deserialize(string sSerial){
                return new AirlockStatus();
            }
        }
        public void Pressurise(
            double TargetPressure=1,
            bool bPressurise=true,
            AirLockRunState RunState=AirLockRunState.Idle)
        {
            PressuriseProg=
                PressuriseProgram(new AirlockStatus(TargetPressure,  bPressurise,  RunState)).GetEnumerator();
        }
        public IEnumerable<AirlockStatus> PressuriseProgram(AirlockStatus alStatus)
        { 
    //TODO: Add blocks loaded test if fail yield return status
    //Add test here and after every existing yield return
    //Test to look like this
    //while(!BlocksLoaded) yield return alStatus;
            while(alStatus.RunState!=AirLockRunState.Done){
                switch(alStatus.RunState){
                case AirLockRunState.ClosingDoors:
                    List<IMyDoor> AllDoors= new List<IMyDoor>(_InnerDoors); 
                    AllDoors.AddRange(_OuterDoors); 
                    List<BlastDoor> AllBlastDoors= new List<BlastDoor>(_InnerBlastDoors); 
                    AllBlastDoors.AddRange(_OuterBlastDoors); 
                    bool LockedClosed=true; 
                    do{ //repeat untill alldoors are closed 
                        LockedClosed=true; 
                        foreach(IMyDoor aDoor in AllDoors){ 
                            if(aDoor.Status==DoorStatus.Closed) aDoor.Enabled=false; 
                            else{ 
                                aDoor.Enabled=true; 
                                aDoor.ApplyAction("Open_Off"); 
                                LockedClosed=false; 
                            } 
                        } 
                        foreach(BlastDoor aDoor in AllBlastDoors){ 
                            if(aDoor.Status ==DoorStatus.Closed) aDoor.Locked=true; 
                            else{ 
                                aDoor.Locked=false; 
                                aDoor.Open(false); 
                                LockedClosed=false; 
                            } 
                        } 
                        yield return alStatus; 
                    }while(!LockedClosed); 
                    alStatus.RunState=
                        alStatus.bPressurise?AirLockRunState.Pressurising:AirLockRunState.Depressurising;
                    if((!alStatus.bPressurise) && (alStatus.TargetPressure>0.045))
                        alStatus.RunState=AirLockRunState.Unlocking;
                    break;
                case AirLockRunState.Pressurising:
                    do{
                        foreach(IMyAirVent aVent in _StorageVents){ 
                            aVent.Depressurize=false; 
                            aVent.Enabled=true; 
                        } 
                        foreach(IMyAirVent aVent in _SupplyVents){ 
                            aVent.Depressurize=false; 
                            aVent.Enabled=(StorageTankCapacity==0); 
                        } 
                        yield return alStatus;
                    }while(Pressure<alStatus.TargetPressure);               
                    alStatus.RunState=AirLockRunState.Unlocking;
                    break;
                case AirLockRunState.Depressurising: 
                    do{ 
                        foreach(IMyAirVent aVent in _SupplyVents){ 
                            aVent.Depressurize=true; 
                            aVent.Enabled=true; 
                        } 
                        foreach(IMyAirVent aVent in _StorageVents){ 
                            aVent.Depressurize=true; 
                            aVent.Enabled=true; 
                        } 
                        yield return alStatus; 
                    }while((Pressure>alStatus.TargetPressure)&&(StorageTankCapacity<1)); 
                    alStatus.RunState=AirLockRunState.Unlocking; 
                    break;
                case AirLockRunState.Unlocking:
                    foreach(IMyAirVent aVent in _StorageVents){
                        aVent.Enabled=false;
                        aVent.Depressurize=false;}
                    foreach(IMyAirVent aVent in _SupplyVents){
                        aVent.Depressurize=false;
                        aVent.Enabled=alStatus.bPressurise;}
                    SetDoorsLocked(false, alStatus.bPressurise); 
                    SetBlastDoorsLocked(false, alStatus.bPressurise); 
                    alStatus.RunState=AirLockRunState.Done;
                    yield return alStatus; 
                    break;
                }
            }
        }
    }
    
     
  2. Malware Master Engineer

    Messages:
    9,549
    Once the script hits the exception, is it resettable or is the world permanently scriptlocked until you restart?
     
  3. Martin R Wolfe Trainee Engineer

    Messages:
    80
    A recompile resets and the world then runs normally again. I have other scripts running on different progblocks and they continue to run even when this one hits exception.
     
  4. Malware Master Engineer

    Messages:
    9,549
    Then this is a genuine instruction overload. You actually are running too much code within one tick... Since there's a rare but known bug related to the yielding state machine technique I just wanted to rule that out.
     
  5. Martin R Wolfe Trainee Engineer

    Messages:
    80
    Adding a few more yield returns in the state machine so it did less each tick did not help. The script runs once each second, no need to run it every tick. Well once every 1.17s or there abouts.
    --- Automerge ---
    Just loading the blocks fist time through main() and then never releasing them also did not help.
    Not sure where else to simplify the code. I am going through line by line at the moment trying to reduce the amount of code run per tick.
     
  6. Malware Master Engineer

    Messages:
    9,549
    Are you sure you don't have an infinite loop somewhere? That's usually the culprit in these situations
     
    • Agree Agree x 1
  7. Martin R Wolfe Trainee Engineer

    Messages:
    80
    Thanks you were correct there was an infinite loop.

    The only change to the state machine I made when I did this was to change the signature of the state machine
    from
    Code:
        public void Pressurise(
            double TargetPressure=1,
            bool bPressurise=true)
        {
            PressuriseProg=
                PressuriseProgram(TargetPressure,  bPressurise).GetEnumerator();
        }
        public IEnumerable<AirlockStatus> PressuriseProgram(
            double TargetPressure=1,
            bool bPressurise=true,
            AirLockRunState RunState=AirLockRunState.ClosingTheDoors
    )
        {
            while(RunState!=AirLockRunState.Done){
                switch(RunState){
                case AirLockRunState.ClosingDoors:
    
    to

    Code:
        public void Pressurise(
            double TargetPressure=1,
            bool bPressurise=true,
            AirLockRunState RunState=AirLockRunState.Idle)
        {
            PressuriseProg=
                PressuriseProgram(new AirlockStatus(TargetPressure,  bPressurise,  RunState)).GetEnumerator();
        }
        public IEnumerable<AirlockStatus> PressuriseProgram(AirlockStatus alStatus)
        {
    
    //TODO: Add blocks loaded test if fail yield return status
    //Add test here and after every existing yield return
    //Test to look like this
    //while(!BlocksLoaded) yield return alStatus;
            while(alStatus.RunState!=AirLockRunState.Done){
                switch(alStatus.RunState){
                case AirLockRunState.ClosingDoors:
    
    And the associated passed values. The serialized version started with RunState=AirLockRunState.Idle this caused the infinite loop.

    I have now corrected this and added a condition to the main loop to deal with the state machine being entered with RunState=AirLockRunState.Idle

    My corrected code is as follows:-
    Code:
        public void Pressurise(
            double TargetPressure=1,
            bool bPressurise=true,
            AirLockRunState RunState=AirLockRunState.ClosingDoors)
        {
            PressuriseProg=
                PressuriseProgram(new AirlockStatus(TargetPressure,  bPressurise,  RunState)).GetEnumerator();
        }
        public IEnumerable<AirlockStatus> PressuriseProgram(AirlockStatus alStatus)
        {
    //TODO: Add blocks loaded test if fail yield return status
    //Add test here and after every existing yield return
    //Test to look like this
    //while(!BlocksLoaded) yield return alStatus;
            while(alStatus.RunState!=AirLockRunState.Done){
                switch(alStatus.RunState){
                case AirLockRunState.Idle:
                    alStatus.RunState=AirLockRunState.ClosingDoors;
                    break;
                case AirLockRunState.ClosingDoors:
    
    --- Automerge ---
    Now that my airlock script is operational I am optimising and documenting it. With regards to optimization which option would be better 1 or 2. My personal preference is option 2 as I like the delegate notation. However is there any computational difference between the two?
    Code:
    //Version 1 current
    foreach(Airlock anAirlock in Airlocks) anAirlock.LoadBlocks(GridTerminalSystem, Me.CubeGrid);
    //Version 2 proposed
    Airlocks.ForEach(anAirlock=>anAirlock.LoadBlocks(GridTerminalSystem, Me.CubeGrid));
     
  8. Malware Master Engineer

    Messages:
    9,549
    @Martin R Wolfe I am so sorry. I did not realize you'd asked another question. I would recommend you use @-tags even when responding to avoid that :)

    Yes, there is a slight computational difference. Your second version calls a method for every loop, the first does not. I wouldn't use that in a very tight performance critical loop, otherwise they're equivalent. Meaning, you're probably just fine using it in most cases.
     
  9. Martin R Wolfe Trainee Engineer

    Messages:
    80
    @Malware
    Thanks for all the help.
    Here is the completed script for comments:-
    Code:
    ///////////////////////////////////////////////////////////////////////////////////////////////         
    //  Name:          Airlock Control Script
    //  Description:  Controls the airlocks on the same grid as the ProgrammableBlock that is
    //                        running this script.
    //         
    //  Author:          Martin Wolfe         
    //  Date:            25 Feb 2017
    //         
    //  Revision history:-         
    //      V2.00  25 Feb 2017      Complete rewrite. Releases blocks between runs and state of
    //                                            airlocks is saved in save game
    //                 
    ///////////////////////////////////////////////////////////////////////////////////////////////         
    //Configuration constants
    private const double StartDelay=4;
    
    //  Name:          enumBlockStatus
    //  Description:  Used in checking and setting the load state of blocks
    //  Type:            private enum         
    private enum enumBlockStatus { 
        BlocksNotLoaded, 
        BlocksLoaded 
    } 
    
    //Private variables
    private DateTime SystemLoadTime;
    private List<Airlock> Airlocks;
    private List<string> saExternalPressureSensors;
    private List<IMyAirVent>ExternalPressureSensors;
    private enumBlockStatus BlockStatus;
    private string ErrorMessage;
    private Dictionary<string,Action<String[]>> Commands;
    
    ///////////////////////////////////////////////////////////////////////////////////////////////         
    //  Name:          Program   
    //  Description:  Constructor - Sets script load time and initialises from storage
    //  Type:            public method         
    //  Parameters:  none                                                    Description: No parameters     
    //  Returns:        none                                                    Description: nothing returned     
    //           
    //  Author:        Martin Wolfe         
    //  Date:            25 Feb 2017         
    //         
    //  Revision history:-         
    //      V1.00  25 Feb 2017    Initial Coding         
    //                 
    public Program() {
        SystemLoadTime=DateTime.Now;
        Commands=new Dictionary<string,Action<String[]>>();
        Commands.Add("REBOOT",REBOOT);
        Commands.Add("TimerPing",TimerPing);
        Commands.Add("Pressurise",Pressurise);
        Commands.Add("PressuriseAll",PressuriseAll);
        Commands.Add("Depressurise",Depressurise);
        Commands.Add("Open",Open);
    
        Initialise(Storage);
    }
    ///////////////////////////////////////////////////////////////////////////////////////////////         
    //  Name:          Save   
    //  Description:  Called on save game. Writes the serialized configuration and current state to
    //                        storage
    //  Type:            public method         
    //  Parameters:  none                                                    Description: No parameters   
    //  Returns:        none                                                    Description: nothing returned     
    //         
    //  Author:          Martin Wolfe         
    //  Date:            25 Feb 2017
    //         
    //  Revision history:-         
    //      V1.00  25 Feb 2017
    //                 
    public void Save() {
        Storage=Serialize().ToString().Trim();
    }
    
    ///////////////////////////////////////////////////////////////////////////////////////////////         
    //  Name:          Main   
    //  Description:  Main entry point to script. Argument takes the form of commands each
    //                        command of the form: "Coomand: Param1, Param2, Param3...;"
    //  Type:            public method         
    //  Parameters: string argument                                  Description: Contains commands for the
    //                                                                                                        script to execute.
    //  Returns:      none                                                    Description: nothing returned   
    //         
    //  Author:        Martin Wolfe         
    //  Date:            25 Feb 2017         
    //         
    //  Revision history:-         
    //      V1.00  25 Feb 2017    Initial Coding         
    //                 
    public void Main(string argument) {
        //Delay by StartDelay to give save game time to fully load
        if((DateTime.Now-SystemLoadTime).TotalSeconds<StartDelay) return; 
        //Extract the commands from the arguments
        List<ParameterWithValue> CommandLines=ParametersWithValuesFromString(argument);
        //Load the blocks to the classes
        if(BlockStatus==enumBlockStatus.BlocksNotLoaded) BlockStatus=LoadBlocks();
    
        string[] Cmds = argument.Trim().Split(';');
        int j=Cmds.Length;
        for(int i=0;i<j;i++){
            string[] Command = Cmds[i].Trim().Split(new char[]{':'},2);
            if(Command[0]!=""){
                try {Commands[Command[0]]((Command.Length==2?Command[1].Trim():"").Split(','));}
                catch(Exception E){
                    if(E.GetType()==typeof(KeyNotFoundException))
                        Echo(Me.CustomName + ": Error unrecognised command: " + Command[0]); 
                    else
                        throw(E); //Just incase it was not caused by index out of bounds in dictionary
                }
            }
        }
        //Free the blocks from the classes
        if(BlockStatus==enumBlockStatus.BlocksLoaded) BlockStatus=UnloadBlocks(); 
    }
    
    #region Commands
    /////////////////////////////////////////////////////////////////////////
    //  Command definitions for command dictionary.
    //  All have the signiture:- void Command(String[] Args)
    /////////////////////////////////////////////////////////////////////////
    private void REBOOT(String[] Args){
        if(Args.Length==0){ 
            Echo(Me.CustomName + ": REBOOTING"); 
            Initialise(Me.CustomData); 
        } 
    }
    private void TimerPing(String[] Args){
        StringBuilder StatusMessage=new StringBuilder(Me.CustomName)
            .AppendFormat(": Status {0}\nExternal pressure: {1}\nAirlock count: {2}",
                ErrorMessage??"Ok",
                ExternalPressureSensors.Count>0
                    ?ExternalPressureSensors[0].GetOxygenLevel().ToString("0.00%"):"Sensor Missing",
                Airlocks.Count); 
        Airlocks.ForEach(aAirlock=>{
            aAirlock.RunAirlock(); 
            StatusMessage.AppendLine().AppendFormat("Airlock: {0}", aAirlock.ToString());});
        Transmit("Stage 1", "Flight Computer", "ShipSealedState: "+Status);
        WriteTextOnDefaultDisplay(StatusMessage);
        Echo(StatusMessage.ToString());
    }
    
    private void Pressurise(String[] Args) {
        for(int i=0;i<Args.Length;i++)
            if(Args[i].Trim()!="")Airlocks.FindAll(x=>x.Name==Args[i].Trim()).ForEach(x=>x.Pressurise(1, true));
    }
    private void PressuriseAll(String[] Args) => Airlocks.ForEach(x=>x.Pressurise(1,  true)); 
    
    private void Depressurise(String[] Args) {
        for(int i=0;i<Args.Length;i++) if(Args[i].Trim()!="") Airlocks.FindAll(x=>x.Name==Args[i].Trim())
            .ForEach(x=>x.Pressurise(ExternalPressure,  false));
    }
    private void Open(String[] Args) {
        for(int i=0;i<Args.Length;i++) Airlocks.FindAll(x=>x.Name==Args[i].Trim()).ForEach(x=>x.Open());
    }
    
    #endregion Commands
    
    bool Transmit(string Grid, string Block, string Message){
        bool Result=false;
        try{
            Transceiver.CustomData="Transmit:"+Grid+","+Block+","+Message+";";
        }catch(Exception E){}
        return Result;
    }
    IMyProgrammableBlock Transceiver {get{
        List<IMyProgrammableBlock> FoundComps=new List<IMyProgrammableBlock>();
        IMyCubeGrid CubeGrid=Me.CubeGrid;
        GridTerminalSystem.GetBlocksOfType<IMyProgrammableBlock>(FoundComps, x=>
            ((x.CubeGrid==CubeGrid)&&(x.CustomName.Trim().IndexOf("Transceiver")!=-1)));
        return FoundComps.Count!=0?FoundComps[0]:null;
    }}
    
    
    ///////////////////////////////////////////////////////////////////////////////////////////////         
    //  Name:          Initialise   
    //  Description:  Initialises the script to the given string.         
    //  Type:            private method         
    //  Parameters: string ConfigString                            Description: Configuration for the script
    //                                                                                                      includes the saved state if
    //                                                                                                      passed from storage
    //  Returns:      none                                                    Description: nothing returned   
    //         
    //  Author:        Martin Wolfe         
    //  Date:            25 Feb 2017
    //         
    //  Revision history:-         
    //      V1.00  25 Feb 2017    Initial Coding         
    //                 
    private void Initialise(string ConfigString){
        //Extract the configuration lines  from ConfigString 
        List<ParameterWithValue> ParameterLines=ParametersWithValuesFromString(ConfigString); 
        //Reset clases
        Airlocks=new List<Airlock>();
        saExternalPressureSensors=new List<string>(); 
        ExternalPressureSensors=new List<IMyAirVent>();
        BlockStatus=enumBlockStatus.BlocksNotLoaded;
    
        //Initialise classes to config
        ErrorMessage=null; 
        foreach(ParameterWithValue aParameter in ParameterLines) 
            try{ 
                switch(aParameter.Name){ 
                case "Airlock":
                    Airlocks.Add(new Airlock(aParameter.Values));
                    break;
                case "ExternalPressureSensor":
                    aParameter.Values.ForEach(SensorName=>{
                        if(String.IsNullOrWhiteSpace(SensorName))
                            throw new Exception("Sensor name error. Name is empty string!");
                        if(!saExternalPressureSensors.Exists(x=>(x==SensorName.Trim())))
                            saExternalPressureSensors.Add(SensorName.Trim());
                        else throw new Exception("Sensor name warning.\nDuplicate name - "+SensorName.Trim());});
                    break;
                default: 
                    break;
                } 
            }catch(Exception Error){
                ErrorMessage=(ErrorMessage??"")+("\n" + Error.Message);
            } 
        if(ErrorMessage!=null)
            ErrorMessage=(Airlocks.Count==0?"BOOT Failure":"BOOT Warning")+ErrorMessage;
        Airlocks=Airlocks.OrderBy(x=>x.Name).ToList();
    }
    ///////////////////////////////////////////////////////////////////////////////////////////////           
    //  Name:          Serialize   
    //  Description:  Serializes the scripts configuration and current run state.           
    //  Type:            private method         
    //  Parameters:  none                                                  Description: No parameters
    //  Returns:      none                                                    Description: nothing returned     
    //           
    //  Author:        Martin Wolfe           
    //  Date:            25 Feb 2017
    //           
    //  Revision history:-           
    //      V1.00  25 Feb 2017    Initial Coding           
    //                 
    private StringBuilder Serialize(){
        StringBuilder Result=new StringBuilder();
        foreach(string sSensor in saExternalPressureSensors)
            Result.AppendLine().AppendFormat("ExternalPressureSensor: {0};", sSensor);
        foreach(Airlock aAirlock in Airlocks) Result.AppendLine().Append(aAirlock.Serialize());
        return Result;
    }
    
    ///////////////////////////////////////////////////////////////////////////////////////////////           
    //  Name:          ParameterWithValue     
    //  Description:  Holds a parameter with a series of values.           
    //  Type:            public structure           
    //           
    //  Author:        Martin Wolfe           
    //  Date:            25 Feb 2017 
    //           
    //  Revision history:-           
    //      V1.00  25 Feb 2017    Initial Coding           
    //                   
    public struct ParameterWithValue {
        // Public fields 
        public string Name; 
        public List<string> Values;
        //  Name:          ParameterWithValue     
        //  Description: Constructor             
        //  Type:            public method
        //  Parameters: string aCommandLine                      Description: Contains name and values
        //  Returns:      none                                                  Description: nothing returned     
        //  Revision history:-             
        //  V1.00  25 Feb 2017    Initial Coding             
        public ParameterWithValue(string aCommandLine){
            List<string> CommandWithParams = new List<string>(aCommandLine.Split(new char[] {':'},2));
            if(CommandWithParams.Count<2) CommandWithParams.Add("");
            Name=CommandWithParams[0];
            Values=new List<string>();
            foreach(string aParameter in (new List<string>(CommandWithParams[1].Trim().Split(',')))){
                if(aParameter.Trim()!="") Values.Add(aParameter.Trim());
            }
        }
    } 
     
    ///////////////////////////////////////////////////////////////////////////////////////////////           
    //  Name:          ParametersWithValuesFromString     
    //  Description:  Creats a list of parameters with values from given string.           
    //  Type:            static public method           
    //  Parameters:  string ParameterString                      Description: The string to get the
    //                                                                                  parameters from.
    //  Returns:      List<ParameterWithValue>                Description: A list of prameters with values     
    //           
    //  Author:        Martin Wolfe           
    //  Date:            25 Feb 2017 
    //           
    //  Revision history:-           
    //      V1.00  25 Feb 2017    Initial Coding           
    //                   
    static public  List<ParameterWithValue> ParametersWithValuesFromString(string ParameterString){
        //Extract the commands from the arguments 
        List<ParameterWithValue> ParametersWithValues=new List<ParameterWithValue>(); 
        foreach(string aCommandLine in (new List<string>(ParameterString.Trim().Split(';')))) 
            if(aCommandLine.Trim()!="") ParametersWithValues.Add(new ParameterWithValue(aCommandLine.Trim())); 
        return ParametersWithValues;
    }
    
    ///////////////////////////////////////////////////////////////////////////////////////////////             
    //  Name:          LoadBlocks     
    //  Description:  Loads the blocks from the grid terminal system that match the configuration and
    //                        are on this programmable blocks grid.
    //  Type:            private method           
    //  Parameters:  None                                                  Description: No parameters
    //  Returns:        enumBlockStatus                              Description: On success BlocksLoaded
    //                                                                                                        On failure BlocksNotLoaded
    //             
    //  Author:          Martin Wolfe             
    //  Date:            25 Feb 2017 
    //             
    //  Revision history:-             
    //      V1.00  25 Feb 2017    Initial Coding             
    //                   
    private enumBlockStatus LoadBlocks(){
        foreach(Airlock anAirlock in Airlocks) anAirlock.LoadBlocks(GridTerminalSystem, Me.CubeGrid);
        List<IMyAirVent> FoundVents = new List<IMyAirVent>(); 
        foreach(string aSensor in saExternalPressureSensors){ 
            GridTerminalSystem.GetBlocksOfType<IMyAirVent>(FoundVents, 
                x=>((x.CustomName==aSensor)&&(x.CubeGrid==Me.CubeGrid))); 
            ExternalPressureSensors.AddRange(FoundVents); 
        }
        return enumBlockStatus.BlocksLoaded;
    }
    
    ///////////////////////////////////////////////////////////////////////////////////////////////             
    //  Name:          UnloadBlocks       
    //  Description:  Unloads the blocks freeing memory and to alow for block changes.             
    //  Type:            private method             
    //  Parameters:  None                                                  Description: No parameters 
    //  Returns:        enumBlockStatus                              Description: On success BlocksNotLoaded
    //                                                                                                        On failure BlocksLoaded
    //             
    //  Author:          Martin Wolfe             
    //  Date:            25 Feb 2017   
    //             
    //  Revision history:-             
    //      V1.00  25 Feb 2017    Initial Coding             
    //                     
    private enumBlockStatus UnloadBlocks(){
        foreach(Airlock anAirlock in Airlocks) anAirlock.UnloadBlocks(); 
        ExternalPressureSensors.Clear(); 
        return enumBlockStatus.BlocksNotLoaded;
    }
    
    ///////////////////////////////////////////////////////////////////////////////////////////////             
    //  Name:          ExternalPressure     
    //  Description:  The external pressure to the grid as read from external air vents.
    //  Type:            public double read only property
    //  Author:          Martin Wolfe             
    //  Date:            25 Feb 2017 
    //  Revision history:-             
    //  V1.00  25 Feb 2017    Initial Coding             
    public double ExternalPressure =>ExternalPressureSensors.Count>0 
                        ?ExternalPressureSensors[0].GetOxygenLevel():0;
    
    
    public DoorStatus Status {get{
        DoorStatus Result=DoorStatus.Closed;
        if(Airlocks.Exists(x=>x.SealState==DoorStatus.Closing)) Result=DoorStatus.Closing;
        if(Airlocks.Exists(x=>x.SealState==DoorStatus.Open)) Result=DoorStatus.Open;
        if(Airlocks.Exists(x=>x.SealState==DoorStatus.Opening)) Result=DoorStatus.Opening;
        return Result; 
    }}
    ///////////////////////////////////////////////////////////////////////////////////////////////               
    //  Name:          BlastDoor       
    //  Description:  Models a blast door as an object by interfaceing with the blocks that form it. 
    //                        The only active block involved in a blast door is a rotor. If a blast door has
    //                        contra rotating/free rotors thse will need to be modeled as a second blast door
    //                        As the open and closed angles are stored in the blastdoor's parameters and are
    //                        not read from the rotors.
    // 
    //  Type:            private class             
    //               
    //  Author:          Martin Wolfe               
    //  Date:            25 Feb 2017   
    //               
    //  Revision history:-               
    //      V1.00  25 Feb 2017    Initial Coding               
    //                     
    private class BlastDoor{
        //Public string property to hold the blast door's name. The set method is private.
        public string Name {get; private set;}=""; 
        //Private fields to hold the open and closed angles in radians
        private double CloseAngle=0; 
        private double OpenAngle=0;
        //Public enumBlockStatus property to hold the block load state. The set method is private.
        public enumBlockStatus BlockStatus{get; private set;}=enumBlockStatus.BlocksNotLoaded; 
        //Private field to hold the loaded blocks 
        private List<IMyMotorStator> _Rotors; 
             
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////   
        //  Name:          BlastDoor         
        //  Description: Constructor               
        //  Type:            public method 
        //  Parameters: List<string> ConfigValues                Description: Contains serialized configuration and state 
        //                                                                                                      of the blastdoor 
        //  Returns:      none                                                  Description: nothing returned         
        //  Revision history:- 
        //  V1.01    26 Feb 2017    Split AddAndReadConfigFrom into a separate method to allow for dynamic 
        //                                      reconfiguration of blastdoor at a later date. 
        //  V1.00  25 Feb 2017    Initial Coding               
        public BlastDoor(string ConfigString){
            _Rotors=new List<IMyMotorStator>();
            CloseAngle=10;   
            OpenAngle=10;     
            AddAndReadConfigFrom(ConfigString); 
        } 
    
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////   
        //  Name:          AddAndReadConfigFrom         
        //  Description: Reads the configuration from configstring and uses it to configure or reconfigure the blast
        //                      door.
        //  Type:            public method 
        //  Parameters: string ConfigString                            Description: Contains serialized configuration of the 
        //                                                                                                      blast door.
        //  Returns:      none                                                  Description: nothing returned         
        //  Revision history:-               
        //  V1.00  26 Feb 2017    Initial Coding               
        public void AddAndReadConfigFrom(string ConfigString){
            List<string> ConfigParams=new List<string>(ConfigString.Trim().Split(new char[]{'|'},3)); 
            Name=ConfigParams[0].Trim();
            if(Name=="") throw new Exception("Blast door name error. Name is empty string");
            if(ConfigParams.Count>1 ) 
                if(ConfigParams[1].Trim()[0]=='r') CloseAngle=double.Parse(ConfigParams[1].Trim().Substring(1).Trim()); 
                else CloseAngle=Math.Round(MathHelper.ToRadians(float.Parse(ConfigParams[1].Trim())),3);   
            if(ConfigParams.Count>2 ) 
                if(ConfigParams[2].Trim()[0]=='r') OpenAngle=double.Parse(ConfigParams[2].Trim().Substring(1).Trim());   
                else OpenAngle=Math.Round(MathHelper.ToRadians(float.Parse(ConfigParams[2].Trim())),3);
            if(Math.Abs(CloseAngle)>(2*Math.PI)) throw new Exception(
                "Blast door angle error. Close angle is invalid\nBlast door - "+Name);
            if(Math.Abs(OpenAngle)>(2*Math.PI)) throw new Exception(
                "Blast door angle error. Open angle is invalid\nBlast door - "+Name);
        }
    
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////   
        //  Name:          Serialize         
        //  Description: Serializes the blastdoor configuration for saving and loading of game
        //  Type:            public method   
        //  Parameters: none                                                  Description: no parameters 
        //  Returns:      StringBuilder                                      Description: the serialized blast door configuration
        //  Revision history:-                 
        //  V1.00  26 Feb 2017    Initial Coding                 
        public StringBuilder Serialize()=>new StringBuilder(Name) 
            .AppendFormat("| r{0}| r{1}", CloseAngle, OpenAngle); 
     
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////     
        //  Name:          ToString           
        //  Description: Converts the blast door's current state to a user readable form. Note that this is only accurate
        //                      with regards to the linked blocks when they are loaded.
        //  Type:            public method   
        //  Parameters: none                                                  Description: no parameters   
        //  Returns:      String                                                  Description: The blast door in string format           
        //  Revision history:-                 
        //  V1.00  26 Feb 2017    Initial Coding                 
        override public string ToString()=>Name+"\n\rStatus: " + Status;   
        ///////////////////////////////////////////////////////////////////////////////////////////////               
        //  Name:          LoadBlocks         
        //  Description:  Loads the blocks from the grid terminal system that match the configuration and 
        //                        are on this programmable blocks grid. 
        //  Type:            public method               
        //  Parameters:  IMyGridTerminalSystem GTS          Description: The grid terminal system 
        //                        IMyCubeGrid CubeGrid                      Description: The grid to load the blocks from 
        //  Returns:        enumBlockStatus                              Description: On success BlocksLoaded 
        //                                                                                                        On failure BlocksNotLoaded 
        //               
        //  Author:        Martin Wolfe               
        //  Date:            25 Feb 2017     
        //               
        //  Revision history:-               
        //      V1.00  25 Feb 2017    Initial Coding               
        //                       
        public enumBlockStatus LoadBlocks(IMyGridTerminalSystem GTS, IMyCubeGrid CubeGrid){ 
            GTS.GetBlocksOfType<IMyMotorStator>(_Rotors,     
                x=>((x.CustomName==Name)&(x.CubeGrid==CubeGrid))); 
            BlockStatus=enumBlockStatus.BlocksLoaded;
            return BlockStatus;
        } 
     
        ///////////////////////////////////////////////////////////////////////////////////////////////                 
        //  Name:          UnloadBlocks         
        //  Description:  Unloads the blocks freeing memory and to alow for block changes.                 
        //  Type:            public method               
        //  Parameters:  None                                                  Description: No parameters   
        //  Returns:        enumBlockStatus                              Description: On success BlocksNotLoaded   
        //                                                                                                        On failure BlocksLoaded   
        //                 
        //  Author:        Martin Wolfe                 
        //  Date:            25 Feb 2017     
        //                 
        //  Revision history:-                 
        //      V1.00  25 Feb 2017    Initial Coding                 
        //                       
        public enumBlockStatus UnloadBlocks(){ 
            BlockStatus=enumBlockStatus.BlocksNotLoaded;   
            _Rotors.Clear();       
            return BlockStatus; 
        } 
             
        ///////////////////////////////////////////////////////////////////////////////////////////////                 
        //  Name:          DoorStatus         
        //  Description:  The open/closed and motion state of the blast door. Not that this is only accurate
        //                        when the blocks are loaded
        //  Type:            public DoorStatus read only property               
        //                 
        //  Author:        Martin Wolfe                 
        //  Date:            25 Feb 2017     
        //                 
        //  Revision history:-                 
        //      V1.00  25 Feb 2017    Initial Coding                 
        //                       
        public DoorStatus Status{ 
            get{ 
                if(Math.Sign(_Rotors[0].TargetVelocity)==Math.Sign(CloseAngle-OpenAngle)){ 
                    if(CloseAngle!=Math.Round(_Rotors[0].Angle,3)) return DoorStatus.Closing; 
                    return DoorStatus.Closed; 
                }else{ 
                    if(OpenAngle!=Math.Round(_Rotors[0].Angle,3)) return DoorStatus.Opening; 
                    return DoorStatus.Open;   
                } 
            } 
        } 
     
        ///////////////////////////////////////////////////////////////////////////////////////////////                 
        //  Name:          Open           
        //  Description:  Opens and closes the blast door.
        //  Type:            public method                 
        //  Parameters:  bool bOpen                                        Description: Open the blast door if true
        //                                                                                                        otherwise close it.     
        //  Returns:        none                                                  Description: nothing returned   
        //                 
        //  Author:        Martin Wolfe                 
        //  Date:            25 Feb 2017       
        //                 
        //  Revision history:-                 
        //      V1.00  25 Feb 2017    Initial Coding                 
        //                         
        public void Open(bool bOpen=true){ 
            if(_Rotors.Count>0){ 
                float TargetVel=Math.Abs(_Rotors[0].TargetVelocity)*
                    (bOpen?(Math.Sign(OpenAngle-CloseAngle)):(Math.Sign(CloseAngle-OpenAngle))); 
                _Rotors.ForEach(x=>x.SetValue<float>("Velocity",TargetVel));
            } 
        } 
     
        ///////////////////////////////////////////////////////////////////////////////////////////////                 
        //  Name:          Locked           
        //  Description:  The lock state of the blast door. Note on get if any rotor is not safety locked false
        //                        is returned otherwise true.
        //  Type:            public bool property                 
        //                 
        //  Author:        Martin Wolfe                 
        //  Date:            25 Feb 2017       
        //                 
        //  Revision history:-                 
        //      V1.00  25 Feb 2017    Initial Coding                 
        //                         
        public bool Locked{ 
            get {return !_Rotors.Exists(x=>!x.SafetyLock);} 
            set {_Rotors.ForEach(x=>x.SafetyLock=value);} 
        }
    } 
    
    ///////////////////////////////////////////////////////////////////////////////////////////////             
    //  Name:          Airlock     
    //  Description:  Models the airlock as an object by interfaceing with the blocks that form it.
    //                        The airlock is controled using the presurise method which creates an instance
    //                        of the PressuriseProgram which is a yield return state machine that runs the
    //                        actual airlock control program. The rest of the airlock class deals with the
    //                        configuration, loading and saving of the airlock as well as reporting on the
    //                        airlock's current state.
    //
    //  Type:            private class           
    //             
    //  Author:        Martin Wolfe             
    //  Date:            25 Feb 2017 
    //             
    //  Revision history:-             
    //      V1.00  25 Feb 2017    Initial Coding             
    //                   
    private class Airlock{
        //Private configuration constants. Airlock vents take the name of the airlock suffixed by the
        //approprate constant.
        private const string SupplySuffix="Supply Vent"; 
        private const string StorageSuffix="Storage Vent";
        private const string StatusSuffix="Status";
    
        //Public enumerations
        public enum AirLockRunState { 
            ClosingDoors,
            OpeningDoors,
            Pressurising, 
            Depressurising, 
            Unlocking, 
            Done, 
            Idle 
        } 
        //Public bodiless properties
        public string Name{get; private set;}
        public enumBlockStatus BlockStatus{get; private set;} 
    
        //Private fields holding the airlock configuration
        private List<string> _saInnerDoors;
        private List<string> _saOuterDoors; 
        private List<string> _saStorageTanks; 
        private List<BlastDoor> _InnerBlastDoors;   
        private List<BlastDoor> _OuterBlastDoors; 
    
        //Private enumerator holding the current run state of the yield return state machine that
        //runs the airlock pressure control program
        private IEnumerator<AirlockStatus> PressuriseProg; 
    
        //Private fields to hold the loaded blocks
        private List<IMyAirVent> _SupplyVents;
        private List<IMyAirVent> _StorageVents;
        private List<IMyDoor> _InnerDoors; 
        private List<IMyDoor> _OuterDoors; 
        private List<IMyGasTank> _StorageTanks;
        private List<IMyTextPanel> _StatusDisplays;
    
        /////////////////////////////////////////////////////////////////////////////////////////////////////////// 
        //  Name:          Airlock       
        //  Description: Constructor             
        //  Type:            public method
        //  Parameters: List<string> ConfigValues                Description: Contains serialized configuration and state
        //                                                                                                      of the airlock
        //  Returns:      none                                                  Description: nothing returned       
        //  Revision history:-
        //  V1.01    26 Feb 2017    Split AddAndReadConfigFrom into a separate method to allow for dynamic
        //                                      reconfiguration of airlock at a later date.
        //  V1.00  25 Feb 2017    Initial Coding             
        public Airlock(List<string> ConfigValues){
    
            _saInnerDoors=new List<string>(); 
            _saOuterDoors=new List<string>(); 
            _saStorageTanks=new List<string>();
            BlockStatus=enumBlockStatus.BlocksNotLoaded;
            _SupplyVents=new List<IMyAirVent>(); 
            _StorageVents=new List<IMyAirVent>();
            _InnerDoors=new List<IMyDoor>(); 
            _OuterDoors=new List<IMyDoor>(); 
            _InnerBlastDoors=new List<BlastDoor>();   
            _OuterBlastDoors=new List<BlastDoor>();
            _StorageTanks=new List<IMyGasTank>();
            _StatusDisplays=new List<IMyTextPanel>();
            PressuriseProg=null;
            AddAndReadConfigFrom(ConfigValues);
        }
    
        /////////////////////////////////////////////////////////////////////////////////////////////////////////// 
        //  Name:          AddAndReadConfigFrom       
        //  Description: Reads the configuration from config values and uses it to configure or add to the airlock
        //  Type:            public method 
        //  Parameters: List<string> ConfigValues                Description: Contains serialized configuration and state
        //                                                                                                      of the airlock
        //  Returns:      none                                                  Description: nothing returned       
        //  Revision history:-               
        //  V1.00  26 Feb 2017    Initial Coding               
        public void AddAndReadConfigFrom(List<string> ConfigValues){
            int i=0; 
            Name = (ConfigValues.Count>i) ? ConfigValues[i].Trim() : "";
            if(Name=="") throw new Exception("Airlock name error. Name is empty string"); 
            for(i++; ConfigValues.Count>i; i++){
                List<string> ConfigValue = new List<string>(ConfigValues[i].Trim().Split(new char[]{':'},2));
                if(ConfigValue.Count>1){
                switch(ConfigValue[0].Trim()){
                    case "InnerDoor":
                        if(String.IsNullOrWhiteSpace(ConfigValue[1]))
                            throw new Exception("Inner door name error. Name is empty string\nAirlock - "+Name);
                        else _saInnerDoors.Add(ConfigValue[1].Trim());
                        break;
                    case "OuterDoor": 
                        if(String.IsNullOrWhiteSpace(ConfigValue[1]))
                            throw new Exception("Outer door name error. Name is empty string\nAirlock - "+Name);
                        else _saOuterDoors.Add(ConfigValue[1].Trim()); 
                        break; 
                    case "InnerBlastDoor": 
                        if(String.IsNullOrWhiteSpace(ConfigValue[1]))
                            throw new Exception("Inner blastdoor name error. Name is empty string\nAirlock - "+Name);
                        else if(!_InnerBlastDoors.Exists(x=>(x.Name==ConfigValue[1].Trim())))
                            _InnerBlastDoors.Add(new BlastDoor(ConfigValue[1].Trim()));   
                        break; 
                    case "OuterBlastDoor": 
                        if(String.IsNullOrWhiteSpace(ConfigValue[1]))
                            throw new Exception("Outer blastdoor name error. Name is empty string\nAirlock - "+Name);
                        else if(!_OuterBlastDoors.Exists(x=>(x.Name==ConfigValue[1].Trim())))
                            _OuterBlastDoors.Add(new BlastDoor(ConfigValue[1].Trim()));
                        break; 
                    case "StorageTank":
                        if(String.IsNullOrWhiteSpace(ConfigValue[1])) 
                            throw new Exception("Storage tank name error. Name is empty string\nAirlock - "+Name); 
                        else if(!_saStorageTanks.Exists(x=>(x==ConfigValue[1].Trim())))
                            _saStorageTanks.Add(ConfigValue[1].Trim());
                        break;
                    case "AirlockStatus":
                        AirlockStatus LoadedStatus;
                        try{ LoadedStatus=new AirlockStatus(ConfigValue[1].Trim());}
                        catch(Exception error){ throw new Exception("Airlock run state load error\n"
                            +error.Message+"\nAirlock - "+Name);}
                        if(LoadedStatus.RunState!=AirLockRunState.Idle)
                            PressuriseProg=PressuriseProgram(LoadedStatus).GetEnumerator();
                        break;
                    default:
                        break;
                    }
                }
            }
            if(_saInnerDoors.Count==0)throw new Exception("Inner door is missing error\nAirlock "+Name);
            if(_saOuterDoors.Count==0)throw new Exception("Outer door is missing error\nAirlock "+Name);
        }
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////   
        //  Name:          Serialize         
        //  Description: Serializes the airlock configuration and current run state for saving and loading of game
        //  Type:            public method 
        //  Parameters: none                                                  Description: no parameters 
        //  Returns:      StringBuilder                                      Description: the serialized airlock configuration and state         
        //  Revision history:-               
        //  V1.00  26 Feb 2017    Initial Coding               
        public StringBuilder Serialize(){
            StringBuilder Result = new StringBuilder("Airlock: ").Append(Name);
            foreach(string sDoor in _saInnerDoors) Result.AppendFormat(",\n\rInnerDoor: {0}", sDoor);
            foreach(string sDoor in _saOuterDoors) Result.AppendFormat(",\n\rOuterDoor: {0}", sDoor);
            foreach(BlastDoor aDoor in _InnerBlastDoors)
                Result.AppendFormat(",\n\rInnerBlastDoor: {0}", aDoor.Serialize());
            foreach(BlastDoor aDoor in _OuterBlastDoors)
                Result.AppendFormat(",\n\rOuterBlastDoor: {0}", aDoor.Serialize());
            foreach(string sTank in _saStorageTanks) Result.AppendFormat(",\n\rStorageTank: {0}",sTank);
            Result.AppendFormat(",\n\r{0};",Status.Serialize()); 
            if(Name=="") Result = new StringBuilder();
            return Result;
        }
     
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////   
        //  Name:          ToString         
        //  Description: Converts the airlocks current state to a user readable form. Note that this is only accurate
        //                      with regards to the lined blocks when they are loaded.
        //  Type:            public method   
        //  Parameters: none                                                  Description: no parameters 
        //  Returns:      String                                                  Description: The airlock in string format         
        //  Revision history:-                 
        //  V1.00  26 Feb 2017    Initial Coding                 
        override public string ToString(){
            StringBuilder toString = new StringBuilder()
                .AppendFormat("{0}\n\rStatus: {1}\n\rPressure: {2:0.00%}\n\rInner Doors: {3}{4}\n\rOuter Doors: {5}{6}",
                Name,Status.RunState,Pressure,
                DoorState(), GetDoorsLocked()?" Locked":" Unlocked",
                DoorState(false), GetDoorsLocked(false)?" Locked":" Unlocked");
            if(_InnerBlastDoors.Count>0) toString.AppendFormat("\n\rInner Blast Doors: {0} {1}",
                BlastDoorState(), GetBlastDoorsLocked()?" Locked":" Unlocked");
            if(_OuterBlastDoors.Count>0) toString.AppendFormat("\n\rOuter Blast Doors: {0} {1}",
                BlastDoorState(false), GetBlastDoorsLocked(false)?" Locked":" Unlocked");
            return toString.ToString();
        }
    
        ///////////////////////////////////////////////////////////////////////////////////////////////             
        //  Name:          LoadBlocks       
        //  Description:  Loads the blocks from the grid terminal system that match the configuration and
        //                        are on this programmable blocks grid.
        //  Type:            public method             
        //  Parameters:  IMyGridTerminalSystem GTS          Description: The grid terminal system
        //                        IMyCubeGrid CubeGrid                      Description: The grid to load the blocks from
        //  Returns:        enumBlockStatus                              Description: On success BlocksLoaded
        //                                                                                                        On failure BlocksNotLoaded
        //             
        //  Author:        Martin Wolfe             
        //  Date:            25 Feb 2017   
        //             
        //  Revision history:-             
        //      V1.00  25 Feb 2017    Initial Coding             
        //                     
        public enumBlockStatus LoadBlocks(IMyGridTerminalSystem GTS,IMyCubeGrid CubeGrid){
            GTS.GetBlocksOfType<IMyAirVent>(_SupplyVents,
                x=>((x.CustomName==(Name+" "+SupplySuffix))&&(x.CubeGrid==CubeGrid))); 
            GTS.GetBlocksOfType<IMyAirVent>(_StorageVents,
                x=>((x.CustomName==(Name+" "+StorageSuffix))&&(x.CubeGrid==CubeGrid)));
            GTS.GetBlocksOfType<IMyTextPanel>(_StatusDisplays,
                x=>((x.CustomName==(Name+" "+StatusSuffix))&&(x.CubeGrid==CubeGrid)));
            List<IMyDoor> FoundDoors=new List<IMyDoor>(); 
            foreach(string sDoorName in _saInnerDoors){
                GTS.GetBlocksOfType<IMyDoor>(FoundDoors, 
                    x=>((x.CustomName==sDoorName)&&(x.CubeGrid==CubeGrid)));
                _InnerDoors.AddRange(FoundDoors);}
            foreach(string sDoorName in _saOuterDoors){ 
                GTS.GetBlocksOfType<IMyDoor>(FoundDoors,   
                    x=>((x.CustomName==sDoorName)&&(x.CubeGrid==CubeGrid))); 
                _OuterDoors.AddRange(FoundDoors);} 
            List<IMyGasTank> FoundTanks = new List<IMyGasTank>();
            foreach(string sTankName in _saStorageTanks){
                GTS.GetBlocksOfType<IMyGasTank>(FoundTanks,
                    x=>((x.CustomName==sTankName)&&(x.CubeGrid==CubeGrid)));
                _StorageTanks.AddRange(FoundTanks);}
            foreach(BlastDoor aBlastDoor in _InnerBlastDoors) aBlastDoor.LoadBlocks(GTS, CubeGrid);
            foreach(BlastDoor aBlastDoor in _OuterBlastDoors) aBlastDoor.LoadBlocks(GTS, CubeGrid); 
            BlockStatus=enumBlockStatus.BlocksLoaded;   
            return BlockStatus;
        }
        ///////////////////////////////////////////////////////////////////////////////////////////////               
        //  Name:          UnloadBlocks       
        //  Description:  Unloads the blocks freeing memory and to alow for block changes.               
        //  Type:            public method             
        //  Parameters:  None                                                  Description: No parameters 
        //  Returns:        enumBlockStatus                              Description: On success BlocksNotLoaded 
        //                                                                                                        On failure BlocksLoaded 
        //               
        //  Author:        Martin Wolfe               
        //  Date:            25 Feb 2017   
        //               
        //  Revision history:-               
        //      V1.00  25 Feb 2017    Initial Coding               
        //                     
        public enumBlockStatus UnloadBlocks(){
            foreach(BlastDoor aBlastDoor in _InnerBlastDoors) aBlastDoor.UnloadBlocks();     
            foreach(BlastDoor aBlastDoor in _OuterBlastDoors) aBlastDoor.UnloadBlocks();
            _SupplyVents.Clear();   
            _StorageVents.Clear();
            _StatusDisplays.Clear(); 
            _InnerDoors.Clear();   
            _OuterDoors.Clear(); 
            _StorageTanks.Clear(); 
            BlockStatus=enumBlockStatus.BlocksNotLoaded;   
            return BlockStatus; 
        } 
        ///////////////////////////////////////////////////////////////////////////////////////////////                 
        //  Name:          Pressure         
        //  Description:  The oxygen level of the airlock.                 
        //  Type:            public double read only property               
        //                 
        //  Author:        Martin Wolfe                 
        //  Date:            25 Feb 2017     
        //                 
        //  Revision history:-                 
        //      V1.00  25 Feb 2017    Initial Coding                 
        //                       
        public double Pressure {
            get {
                double Result=-1;
                if(BlockStatus==enumBlockStatus.BlocksLoaded){
                    List<IMyAirVent> Vents=new List<IMyAirVent>(_SupplyVents);
                    Vents.AddRange(_StorageVents);
                    if(Vents.Count>0) Result=Vents[0].GetOxygenLevel();
                }
                return Result;
            }
        }
       
        ///////////////////////////////////////////////////////////////////////////////////////////////                 
        //  Name:          StorageTankCapacity         
        //  Description:  The capacity of the oxygen storage system. A range of 0 to 1 with 0 being empty
        //                        and 1 being full.
        //  Type:            public double read only property                 
        //                 
        //  Author:        Martin Wolfe                 
        //  Date:            25 Feb 2017       
        //                 
        //  Revision history:-                 
        //      V1.00  25 Feb 2017    Initial Coding                 
        //                         
        public double StorageTankCapacity {
            get{
                double _TankCapacity=1;
                if(_StorageTanks.Count>0){
                    _TankCapacity=0;
                    foreach(IMyGasTank aTank in _StorageTanks) _TankCapacity+=aTank.FilledRatio;
                    _TankCapacity/=_StorageTanks.Count;
                }
                return _TankCapacity;
            }
        }
        public DoorStatus SealState {get{
            DoorStatus Result=DoorStatus.Closed;
            if((DoorState(false)==DoorStatus.Closing)||(BlastDoorState(false)==DoorStatus.Closing))
                Result=DoorStatus.Closing;
            if((DoorState(false)==DoorStatus.Open)||(BlastDoorState(false)==DoorStatus.Open))
                Result=DoorStatus.Open; 
            if((DoorState(false)==DoorStatus.Opening)||(BlastDoorState(false)==DoorStatus.Opening))
                Result=DoorStatus.Opening; 
            return Result;
        }}
        ///////////////////////////////////////////////////////////////////////////////////////////////                 
        //  Name:          DoorState         
        //  Description:  The open/closed and motion state of the airlock doors.                 
        //  Type:            public method               
        //                 
        //  Parameters:  bool Inner=true                                  Description: true for inner doors false for outer
        //  Returns:        DoorStatus                                        Description: The open/closed and motion state
        //                                                                                                        If no doors of the type are fitted
        //                                                                                                        DoorStatus.Closed is returned.
        //  Author:        Martin Wolfe                 
        //  Date:            25 Feb 2017     
        //                 
        //  Revision history:-                 
        //      V1.00  25 Feb 2017    Initial Coding                 
        //                       
        public DoorStatus DoorState(bool Inner=true){
            List<IMyDoor> Doors = Inner ? _InnerDoors : _OuterDoors;
            DoorStatus doorState=DoorStatus.Closed; 
            if(Doors.Exists(x=>(x.Status==DoorStatus.Open))) doorState=DoorStatus.Open;
            if(Doors.Exists(x=>(x.Status==DoorStatus.Closing))) doorState=DoorStatus.Closing;
            if(Doors.Exists(x=>(x.Status==DoorStatus.Opening))) doorState=DoorStatus.Opening;
            return doorState;
        }
        ///////////////////////////////////////////////////////////////////////////////////////////////                 
        //  Name:          BlastDoorState           
        //  Description:  The open/closed and motion state of the airlock blast doors.                 
        //  Type:            public method                 
        //                 
        //  Parameters:  bool Inner=true                                  Description: true for inner doors false for outer
        //  Returns:        doorState                                          Description: The open/closed and motion state   
        //                                                                                                        If no doors of the type are fitted
        //                                                                                                        DoorStatus.Closed is returned.
        //  Author:        Martin Wolfe                 
        //  Date:            25 Feb 2017       
        //                 
        //  Revision history:-                 
        //      V1.00  25 Feb 2017    Initial Coding                 
        //                         
        public DoorStatus BlastDoorState(bool Inner=true){ 
            List<BlastDoor> Doors = Inner ? _InnerBlastDoors : _OuterBlastDoors;
            DoorStatus doorState=DoorStatus.Closed; 
            if(Doors.Exists(x=>(x.Status==DoorStatus.Open))) doorState=DoorStatus.Open; 
            if(Doors.Exists(x=>(x.Status==DoorStatus.Closing))) doorState=DoorStatus.Closing;
            if(Doors.Exists(x=>(x.Status==DoorStatus.Opening))) doorState=DoorStatus.Opening;
            if(Doors.Count==0) doorState=DoorState(Inner);
            return doorState; 
        } 
        ///////////////////////////////////////////////////////////////////////////////////////////////                   
        //  Name:          GetDoorsLocked           
        //  Description:  Gets the lock state of the airlock doors.                   
        //  Type:            public method                 
        //                   
        //  Parameters:  bool Inner=true                                  Description: true for inner doors false for outer 
        //  Returns:        bool                                                    Description: The locked state of the airlock doors.
        //                                                                                                        If any door is unlocked false is returned
        //                                                                                                        otherwise true.
        //  Author:        Martin Wolfe                   
        //  Date:            25 Feb 2017       
        //                   
        //  Revision history:-                   
        //      V1.00  25 Feb 2017    Initial Coding                   
        //                         
        public bool GetDoorsLocked(bool Inner=true) =>
            !(Inner ? _InnerDoors : _OuterDoors).Exists(x=>(x.Enabled==true));
     
        ///////////////////////////////////////////////////////////////////////////////////////////////                   
        //  Name:          SetDoorsLocked             
        //  Description:  Locks all the airlock doors.                   
        //  Type:            public method                   
        //                   
        //  Parameters:  bool bLocked                                    Description: The new lock state for the doors
        //                        bool Inner=true                                  Description: true for inner doors false for outer 
        //  Returns:        none
        //  Author:          Martin Wolfe                   
        //  Date:            25 Feb 2017         
        //                   
        //  Revision history:-                   
        //      V1.00  25 Feb 2017    Initial Coding                   
        //                           
        public void SetDoorsLocked(bool bLocked, bool Inner=true) { 
            foreach(IMyDoor aDoor in (Inner?_InnerDoors:_OuterDoors)) aDoor.Enabled=!bLocked; 
        }
        ///////////////////////////////////////////////////////////////////////////////////////////////                   
        //  Name:          GetBlastDoorsLocked             
        //  Description:  Gets the lock state of the airlock blast doors.                   
        //  Type:            public method                   
        //                   
        //  Parameters:  bool Inner=true                                  Description: true for inner doors false for outer 
        //  Returns:        bool                                                    Description: The locked state of the airlock blast doors.
        //                                                                                                        If any door is unlocked false is returned
        //                                                                                                        otherwise true.
        //  Author:        Martin Wolfe                   
        //  Date:            25 Feb 2017         
        //                   
        //  Revision history:-                   
        //      V1.00  25 Feb 2017    Initial Coding                   
        //                           
        public bool GetBlastDoorsLocked(bool Inner=true) =>
            !(Inner ? _InnerBlastDoors : _OuterBlastDoors).Exists(x=>(x.Locked==false));
        ///////////////////////////////////////////////////////////////////////////////////////////////                     
        //  Name:          SetBlastDoorsLocked             
        //  Description:  Locks all the airlock blast doors.                     
        //  Type:            public method                   
        //                     
        //  Parameters:  bool bLocked                                    Description: The new lock state for the doors
        //                        bool Inner=true                                  Description: true for inner doors false for outer   
        //  Returns:        none
        //  Author:          Martin Wolfe                     
        //  Date:            25 Feb 2017         
        //                     
        //  Revision history:-                     
        //      V1.00  25 Feb 2017    Initial Coding                     
        //                           
        public void SetBlastDoorsLocked(bool bLocked, bool Inner=true) { 
            foreach(BlastDoor aDoor in (Inner?_InnerBlastDoors:_OuterBlastDoors)) aDoor.Locked=bLocked; 
        }
     
        ///////////////////////////////////////////////////////////////////////////////////////////////                     
        //  Name:          RunAirlock               
        //  Description:  Runs 1 tick of the airlock program.                     
        //  Type:            public method                     
        //                     
        //  Parameters:  none                                                  Description: Nothing 
        //  Returns:        none                                                  Description: Nothing 
        //  Author:          Martin Wolfe                     
        //  Date:            25 Feb 2017           
        //                     
        //  Revision history:-                     
        //      V1.00  25 Feb 2017    Initial Coding                     
        //                             
        public void RunAirlock(){
            if((PressuriseProg != null)
                && ((!PressuriseProg.MoveNext())
                        ||(PressuriseProg.Current.RunState==AirLockRunState.Done)))
                PressuriseProg = null;
            _StatusDisplays.ForEach(x=>{
                StringBuilder StatusText=new StringBuilder()
                    .AppendLine(Name)
                    .AppendLine(Status.RunState.ToString())
                    .AppendFormat("O2 Pressure: {0:##0.0%}",Pressure);
                x.WritePublicText(StatusText.ToString(), false);
                x.ShowPublicTextOnScreen();
            });
        }
        ///////////////////////////////////////////////////////////////////////////////////////////////                   
        //  Name:          Status           
        //  Description:  Gives the current run state and target conditions of the airlock.
        //  Type:            public AirlockStatus read only property                 
        //                   
        //  Author:        Martin Wolfe                   
        //  Date:            25 Feb 2017       
        //                   
        //  Revision history:-                   
        //      V1.00  25 Feb 2017    Initial Coding                   
        //                         
        public AirlockStatus Status => (PressuriseProg == null)
            ?  (new AirlockStatus(1,true,AirLockRunState.Idle))
            : PressuriseProg.Current;
        ///////////////////////////////////////////////////////////////////////////////////////////////             
        //  Name:          AirlockStatus     
        //  Description:  Holds an airlock status and run state             
        //  Type:            public structure           
        //             
        //  Author:        Martin Wolfe             
        //  Date:            25 Feb 2017 
        //             
        //  Revision history:-             
        //      V1.00  25 Feb 2017    Initial Coding             
        //                   
        public struct AirlockStatus{
            public double TargetPressure;
            public bool bPressurise;
            public AirLockRunState RunState;
            //  Name:          AirlockStatus       
            //  Description: Constructor             
            //  Type:            public method
            //  Parameters: double targetPressure=1                  Description: The target pressure 
            //                      bool BPressurise=true                      Description: If true pressurise to target
            //                                                                                                      otherwise depressurise to target.
            //                      AirLockRunState runState=AirLockRunState.Idle
            //                                                                                  Description: The run stage
            //  Returns:      none                                                  Description: nothing returned       
            //  Revision history:-             
            //  V1.00  25 Feb 2017    Initial Coding             
            public AirlockStatus( 
                double targetPressure=1, 
                bool BPressurise=true, 
                AirLockRunState runState=AirLockRunState.Idle)
            {
                TargetPressure=targetPressure;
                bPressurise=BPressurise;
                RunState=runState;
            }
           
            //  Name:          AirlockStatus       
            //  Description: Constructor               
            //  Type:            public method 
            //  Parameters: string sSerial                                    Description: Contains the serialized initialization values 
            //  Returns:      none                                                  Description: nothing returned       
            //  Revision history:-               
            //  V1.00  25 Feb 2017    Initial Coding               
            public AirlockStatus(string sSerial){
                List<string> saSerial=new List<string>(sSerial.Trim().Split(new char[] {'|'},3)); 
                if(saSerial.Count<3) throw new Exception(
                    "Run state desrialize only found "+ saSerial.Count+ " parameters");
                RunState=AirLockRunState.Idle;
                foreach(var value in Enum.GetValues(typeof(AirLockRunState))){
                    if(((AirLockRunState)value).ToString()==saSerial[2].Trim())
                        RunState=(AirLockRunState)value;
                }
                TargetPressure=double.Parse(saSerial[0].Trim());
                bPressurise=bool.Parse(saSerial[1].Trim());
            }
    
            //  Name:          ToString           
            //  Description: Converts the airlock status to a user readable form.
            //  Type:            public method   
            //  Parameters: none                                                    Description: no parameters   
            //  Returns:      String                                                  Description: The AirlockStatus in string format           
            //  Revision history:-                 
            //  V1.00  25 Feb 2017    Initial Coding                 
            public override string ToString()=>TargetPressure.ToString()+","+bPressurise+","+RunState;
            public StringBuilder Serialize() =>new StringBuilder()
                .AppendFormat("AirlockStatus: {0}| {1}| {2}", TargetPressure, bPressurise, RunState);
        }
    
        ///////////////////////////////////////////////////////////////////////////////////////////////                       
        //  Name:          Open               
        //  Description:  Runs the yield state machine to open the airlock doors.                       
        //  Type:            public method                     
        //                       
        //  Parameters:  none                                                  Description: Nothing 
        //  Returns:        none                                                  Description: Nothing 
        //  Author:          Martin Wolfe                       
        //  Date:            27 Feb 2017           
        //                       
        //  Revision history:-                       
        //      V1.00  27 Feb 2017    Initial Coding                       
        //                             
        public void Open() => PressuriseProg=
            PressuriseProgram(new AirlockStatus(1,  true,  AirLockRunState.OpeningDoors)).GetEnumerator(); 
        ///////////////////////////////////////////////////////////////////////////////////////////////                       
        //  Name:          Pressurise                 
        //  Description:  Runs the yield state machine to change the airlock pressure
        //  Type:            public method                       
        //                       
        //  Parameters:  double TargetPressure=1                  Description: Target pressure of the airlock   
        //                        bool bPressurise=true                      Description: Pressurise to target if true otherwise
        //                                                                                                        depressurise
        //  Returns:        none                                                  Description: Nothing   
        //  Author:          Martin Wolfe                       
        //  Date:            27 Feb 2017             
        //                       
        //  Revision history:-                       
        //      V1.00  27 Feb 2017    Initial Coding                       
        //                               
        public void Pressurise( double TargetPressure=1, bool bPressurise=true) => PressuriseProg=
            PressuriseProgram(new AirlockStatus(TargetPressure, bPressurise,
            AirLockRunState.ClosingDoors)).GetEnumerator();
    
        ///////////////////////////////////////////////////////////////////////////////////////////////                         
        //  Name:          PressuriseProgram                 
        //  Description:  Yield return state machine to run processes on the airlock
        //  Type:            public method                       
        //                         
        //  Parameters:  AirlockStatus alStatus                      Description: Initial state of machine
        //  Returns:        IEnumerable<AirlockStatus>            Description: An enumerator to run the state
        //                                                                                  machine   
        //  Author:          Martin Wolfe                         
        //  Date:            25 Feb 2017             
        //                         
        //  Revision history:-                         
        //      V1.00  25 Feb 2017    Initial Coding                         
        //                               
        public IEnumerable<AirlockStatus> PressuriseProgram(AirlockStatus alStatus)
        { 
            while(BlockStatus!=enumBlockStatus.BlocksLoaded) yield return alStatus;
            List<IMyDoor> AllDoors;
            List<BlastDoor> AllBlastDoors;
            while(alStatus.RunState!=AirLockRunState.Done){
                switch(alStatus.RunState){
                case AirLockRunState.Idle:
                    alStatus.RunState=AirLockRunState.ClosingDoors;
                    break;
                case AirLockRunState.ClosingDoors:
                    AllDoors= new List<IMyDoor>(_InnerDoors); 
                    AllDoors.AddRange(_OuterDoors); 
                    AllBlastDoors= new List<BlastDoor>(_InnerBlastDoors);
                    AllBlastDoors.AddRange(_OuterBlastDoors); 
                    bool LockedClosed=true; 
                    do{ //repeat untill alldoors are closed 
                        LockedClosed=true; 
                        foreach(IMyDoor aDoor in AllDoors){ 
                            if(aDoor.Status==DoorStatus.Closed) aDoor.Enabled=false; 
                            else{ 
                                aDoor.Enabled=true; 
                                if(aDoor.Status!=DoorStatus.Closing)aDoor.ApplyAction("Open_Off"); 
                                LockedClosed=false; 
                            } 
                        } 
                        foreach(BlastDoor aDoor in AllBlastDoors){ 
                            if(aDoor.Status ==DoorStatus.Closed) aDoor.Locked=true; 
                            else{ 
                                aDoor.Locked=false; 
                                if(aDoor.Status!=DoorStatus.Closing) aDoor.Open(false); 
                                LockedClosed=false; 
                            } 
                        } 
                        do yield return alStatus; while(BlockStatus!=enumBlockStatus.BlocksLoaded);
                    }while(!LockedClosed); 
                    alStatus.RunState=
                        alStatus.bPressurise?AirLockRunState.Pressurising:AirLockRunState.Depressurising;
                    if((!alStatus.bPressurise) && (alStatus.TargetPressure>0.045))
                        alStatus.RunState=AirLockRunState.Unlocking;
                    break;
                case AirLockRunState.Pressurising:
                    do{
                        foreach(IMyAirVent aVent in _StorageVents){ 
                            aVent.Depressurize=false; 
                            aVent.Enabled=true; 
                        } 
                        foreach(IMyAirVent aVent in _SupplyVents){ 
                            aVent.Depressurize=false; 
                            aVent.Enabled=(StorageTankCapacity==0); 
                        } 
                        yield return alStatus;
                    }while(Pressure<(alStatus.TargetPressure-0.001));  //Modified to alow for oxygen usage during pres
                    alStatus.RunState=AirLockRunState.Unlocking;
                    break;
                case AirLockRunState.Depressurising: 
                    do{ 
                        foreach(IMyAirVent aVent in _SupplyVents){ 
                            aVent.Depressurize=true; 
                            aVent.Enabled=true; 
                        } 
                        foreach(IMyAirVent aVent in _StorageVents){ 
                            aVent.Depressurize=true; 
                            aVent.Enabled=true; 
                        } 
                        do yield return alStatus; while(BlockStatus!=enumBlockStatus.BlocksLoaded);
                    }while((Pressure>alStatus.TargetPressure)&&(StorageTankCapacity<1)); 
                    alStatus.RunState=AirLockRunState.Unlocking; 
                    break;
                case AirLockRunState.Unlocking:
                    foreach(IMyAirVent aVent in _StorageVents){
                        aVent.Enabled=false;
                        aVent.Depressurize=false;}
                    foreach(IMyAirVent aVent in _SupplyVents){
                        aVent.Depressurize=false;
                        aVent.Enabled=alStatus.bPressurise;}
                    SetDoorsLocked(false, alStatus.bPressurise); 
                    SetBlastDoorsLocked(false, alStatus.bPressurise); 
                    alStatus.RunState=AirLockRunState.Done;
                    yield return alStatus; 
                    break;
                case AirLockRunState.OpeningDoors:
                    AllDoors= new List<IMyDoor>(_InnerDoors);   
                    AllDoors.AddRange(_OuterDoors);   
                    AllBlastDoors= new List<BlastDoor>(_InnerBlastDoors);   
                    AllBlastDoors.AddRange(_OuterBlastDoors);   
    
                    bool Open;   
                    do{ //repeat untill alldoors are open     
                        Open=true;
                        AllDoors.FindAll(x=>(x.Enabled&&
                            ((x.Status==DoorStatus.Closed)||(x.Status==DoorStatus.Closing)))).ForEach(x=>{
                            x.ApplyAction("Open_On");
                            Open=false;});
                        AllBlastDoors.FindAll(x=>((!x.Locked)&&
                            ((x.Status==DoorStatus.Closed)||(x.Status==DoorStatus.Closing)))).ForEach(x=>{
                            x.Open(true);
                            Open=false;});
                        do yield return alStatus; while(BlockStatus!=enumBlockStatus.BlocksLoaded);   
                    }while(!Open);   
                    alStatus.RunState=AirLockRunState.Done; 
                    yield return alStatus;   
                    break;
                }
            }
        }
    }
    
    void WriteTextOnDefaultDisplay(StringBuilder Text,bool AppendText=false){ 
        try{ 
            DefaultDisplay.WritePublicText(Text, AppendText);     
            DefaultDisplay.ShowPublicTextOnScreen();         
        }catch(Exception E){}; 
    } 
     
    IMyTextPanel DefaultDisplay{ get { 
        IMyCubeGrid CubeGrid=Me.CubeGrid;       
        Quaternion aQuaternion; 
        List<IMyTextPanel> Displays = new List<IMyTextPanel>();     
        Me.Orientation.GetQuaternion(out aQuaternion);       
        Vector3I TargetLoc= Me.Position+Vector3I.Transform(new Vector3I(0,1,1),aQuaternion);     
        GridTerminalSystem.GetBlocksOfType<IMyTextPanel>(Displays,       
            x=>((x.CubeGrid==CubeGrid)&&(x.Position==TargetLoc))); 
        return Displays.Count!=0?Displays[0]:null; 
    }}
    
     
  10. Malware Master Engineer

    Messages:
    9,549
    @Martin R Wolfe I'm afraid your chosen coding standard makes it far too difficult to read for me as a seasoned C# programmer. Each programming language has its set of accepted standards. You're not too far off, but there's too little whitespace, you're packing your code together too much, and you have no clear naming conventions (mixing up PascalCased and camelCased naming in an inconsistent way). I would recommend you reading through common C# coding standards. Consistency is key.
    https://msdn.microsoft.com/en-us/library/ff926074.aspx

    Your comments, while thorough, are wasting a lot of limited characters, and once you start writing bigger scripts they'll get in your way... and C# even has a standardized documentation comment standard. However for PB scripts you'll probably want to stick to simple line and block comments. Well written code documents itself though. Write your code in a clear and consise manner, and comments are superfluous.
     
  11. Martin R Wolfe Trainee Engineer

    Messages:
    80
    Thanks for your advice. To some extent my coding shows my history. Starting with BBC B basic where you had to use abbreviations and pack the code. Then when I learnt C the teacher wanted large comment blocks on every function and method. This has left me with some bad habits.
     
  12. Malware Master Engineer

    Messages:
    9,549
    Consider this: Why are programming teachers teachers and not programmers? I've programmed for a long time. I've learned that what one learns at school about programming is nonsense and usually years outdated. The only reason I got an education in software engineering is to get the papers. Obviously this is quite regional, might not be where you live ;)
     
  13. Martin R Wolfe Trainee Engineer

    Messages:
    80
    True enough. But then I don't code for a living so I never learned good real world coding standards just academic ones where the teachers were more interested in my thoughts than actual working code. Over half the marks on that C course were for the content of the remarks.
     
Thread Status:
This last post in this thread was made more than 31 days old.