Which timer library to use for several seconds of delay

Can you create a function inside of loop() & reference that function within loop()? I suspect not, & I'd rather abstract these functions out of the main loop for readability & ease of use. I'm only doing 3 things, but which I do & when are determined after a few criteria, so it might be 1 thing, it might be 1 set of 2 or it might be the other set of 2, so I'd rather not just copy/paste the code & risk a botched copy/paste later on down the road.

The loop I am using in the functions pulseOutHigh and -Low winds up being something I came up with after I realized that polling millis() didn't really work outside the main loop the same way as inside the loop. Turns out it stops the program just like a delay(), which is not what I want to do.

Is there a way to modify pulseOutHigh and -Low allow for the polling timer?

#include <ServoDecode.h>

char * stateStrings[] = {"NOT_SYNCHED", "ACQUIRING", "READY", "in Failsafe"};
  
// Define readable non-backwards states for relays.
// This is because relays are active LOW.  That means you can ground the pin and activate the relay.
const byte relayOn = 0;
const byte relayOff = 1;

// Give each relay friendly name and map to Arduino Digital I/O pin number
const byte relay1 = 2;  // wire 1 of the engine kill actuator.  Pulse to allow engine to run.
const byte relay2 = 3;  // wire 2 of the engine kill actuator.  Pulse to kill engine.
const byte relay3 = 4;  // brake controller
const byte relay4 = 5;  // currently unused

// For reading/feedback purposes, compare to the on off constants above
byte relay1State;
byte relay2State;
byte relay3State;
byte relay4State;
                                    // seconds * milliseconds, for readibility
const unsigned long idleEngineKill = 10 * 1000; // delay to kill the motor 
const unsigned long idleBrakesEngage = 2 * 1000;
const unsigned long engineStartPeriod = 15 * 1000; // length of time allowed on a new connection to allow for starting the mower engine
long whatTimeIsIt = 0;
long userInputTimer = 0;
boolean timedOut = false;

// Initialize to zero to give it an obvious out of range starting value
// valid values will be from 1000 - 2000 with maybe some spillage in either direction

int curVal[7] = {0, 0, 0, 0, 0, 0, 0}; // Current values of the channel signals
int prevVal[7] = {0, 0, 0, 0, 0, 0, 0}; // Previous values of the channel signals
int arraySize = 7;
int numChannels = 6; // size of both arrays, used for feedback purposes

const int chChangeThreshold = 15; // Used to determine if there was user input on a given channel
boolean firstTimeOn = true;
boolean firstTimeOff = true;
boolean firstInput = true;
boolean engineShouldBeOff = true;
boolean brakesShouldBeOff = false;
byte throttleChannel = 3; // sets the throttle channel to remove it from idle calculations.  Set to 0 to include all channels in idle calculations.  This is selectable as the transmitter is programmable.


void setup(){
  
  //ServoDecode setup
  ServoDecode.begin();
  ServoDecode.setFailsafe(3,1234); // set channel 3 failsafe pulse width
  
  // Relay Setup Initialize Pins so relays are inactive at reset
  digitalWrite(relay1, relayOff);
  digitalWrite(relay2, relayOff);
  digitalWrite(relay3, relayOff);
  digitalWrite(relay4, relayOff);
  relay1State = relayOff;
  relay2State = relayOff;
  relay3State = relayOff;
  relay4State = relayOff;
  
  // THEN set pins as outputs
  pinMode(relay1, OUTPUT);   
  pinMode(relay2, OUTPUT);  
  pinMode(relay3, OUTPUT);  
  pinMode(relay4, OUTPUT);    
  delay(1000); // Check that all relays are inactive at Reset
  
  // Prevent motor from starting and engage breaks
  engineKill();
  brakesEngage();

} // end setup()


void loop(){

  if(ServoDecode.getState()!= READY_state) { // transmitter is off or out of range
    if(firstTimeOff){
      engineKill(); 
      brakesEngage(); 
      whatTimeIsIt = 0; 
      timedOut = false;
      firstTimeOff = false;
      firstTimeOn = true;
    } // end if(firsTimeOff)
  } // end if() transmitter not ready
  
  if(ServoDecode.getState()== READY_state) { // transmitter is on
    
    if (firstTimeOn){
      firstTimeOn = false;
      firstTimeOff = true;
      engineAllow();
      brakesEngage();
      timedOut = false;
    } //enf if() firstTimeOn
    
    if(!userInput() && !timedOut){
      if(firstInput){// test if first entry into this path
        userInputTimer = millis(); // engage a timer
        firstInput = false;
      } //end firstInput
      
      if ( (millis() >= (userInputTimer + idleBrakesEngage)) && !firstTimeOn ){ // if idleBrakesEngage seconds has passed since no user input engage the brakes
        brakesEngage();
        // userInputTimer = 0;
      } // end  
      
      if ( (millis() >= (userInputTimer + idleEngineKill)) && !firstTimeOn ){ // if idleEngineKill seconds has passed since no user input, kill the motor and engage the brakes
        engineKill();
        brakesEngage();
        userInputTimer = 0;
        firstInput = true;
        timedOut = true;
      } // end 
      
    } // end if no input
    
    if(userInput()){
      userInputTimer = 0;
      firstInput = true;
      brakesDisengage();
      timedOut = false;
    } // end if() userInput()
    
  } // end if() transmitter ready
  
} // end main loop()

void engineKill(){
  
  if(relay1State == relayOn){ // just in case it was on for some random reason
    digitalWrite(relay1, relayOff); // prevents frying relays and/or actuators
    relay1State = relayOff;
  } // end safety check
  
  if(relay2State == relayOff){ // if it's already on, no need to do anything, so only do something if its off
    relay2State = relayOn;
    pulseOutLow(relay2, 500);
    relay2State = relayOff;
  } // end if()
  engineShouldBeOff = true;
  //digitalWrite(relay2, relayOn); // eventually pulse it, but for now just latch it
} // end engineKill

void engineAllow(){
  if(relay2State == relayOn){
    digitalWrite(relay2, relayOff); // just in case it was on for some random reason; prevents frying relays and/or actuators
    relay2State = relayOff;
  } // end safety check
  
  if(relay1State == relayOff){ // if it's already on, no need to do anything, so only do something if its off
    relay1State = relayOn;
    pulseOutLow(relay1, 500);
    relay1State = relayOff;
  } // end if()
  engineShouldBeOff = false;
  //digitalWrite(relay1, relayOn); // eventually pulse it, but for now just latch it
} // end instantEngineKill

void brakesEngage(){
  
  if(relay3State == relayOn){ // if it's already onn, no need to do anything, so only do something if its off
    digitalWrite(relay3, relayOff); // engages the brakes by removing power, allowing the mower to move
    relay3State = relayOff;
  } // end if()
  brakesShouldBeOff = true;
} // end brakesEngage

void brakesDisengage(){
  
  if(relay3State == relayOff){ // if it's already on, no need to do anything, so only do something if its on
    digitalWrite(relay3, relayOn); // disengages the brakes by applying voltage
    relay3State = relayOn;
  } // end if()
  brakesShouldBeOff = false;
} // end brakesDisengage

void pulseOutLow(int pin, long duration){ // pulses a digital output for a certain length of time, meant for active low relays
  unsigned long timeStart = millis();
  boolean exitThis = false;
  digitalWrite(pin, LOW);
  while(!exitThis){
    if(millis() >= (timeStart + duration)){
      digitalWrite(pin, HIGH);
      exitThis = true;
    } // end if
  } // end while()
} // end pulseOutLow

void pulseOutHigh(int pin, long duration){ // pulses a digital output for a certain length of time
  unsigned long timeStart = millis();
  boolean exitThis = false;
  digitalWrite(pin, HIGH);
  while(!exitThis){
    if(millis() >= (timeStart + duration)){
      digitalWrite(pin, LOW);
      exitThis = true;
    } // end if
  } // end while()
} // end pulseOutHIGH

boolean userInput(){ // tests for user input via the transmitter
  boolean returnVal = false;
  boolean curPrevChange[arraySize]; // used an array to test for ignore channel.  May not be needed but makes it simpler
  
  // move current values to previous array, sample current values, populate current array
  for ( int i = 1; i <= numChannels; i++ ){
    prevVal[i] = curVal[i];
    curVal[i] = ServoDecode.GetChannelPulseWidth(i); // GetChannelPulseWidth technically requires a byte, but the number won't get high enough to cause problems
  } // end move old values to proper variable
  
     // Compute if there was a change
  for ( int i = 1; i <= numChannels; i++ ){
    if(i==throttleChannel){
      curPrevChange[i] =  abs(curVal[i] - 1500) > chChangeThreshold;
      // curPrevChange[i] = false; // set to false b/c this is excluded from calculations
    } // end if      
    else{ curPrevChange[i] =  abs(curVal[i] - prevVal[i]) > chChangeThreshold;  }
         //end else
    returnVal = returnVal | curPrevChange[i]; // if anything changes, the value will go true
    } // end for() of compare channel values from each array and populate curPrevChange array
  return returnVal;
} // end userInput