I was very happy to see that almost every important aspect of the robust PID algorithm was already covered by many people.
The most important thing to know for PID exit condition, is whether it is intended for user control or autonomous.
In case of the user control - PID shouldn’t have its own while loop, but the main while(true) loop that has all the code for checking user inputs would call PID function once in 10-20 mS and let it determine necessary motor power. Variables like accumulated integral and prevSensorValue need to persist outside of the PID function.
This way if user presses a key, the PID control could be easily interrupted and user could take over with manual control. You code will have to have a variable that indicates if it is in the PID or manual control state (for each specific subsystem).
This could be very useful for cases when you want to press a macro key to set lift target and continue driving while the lift moves to target position under the control of PID. If you have multiple subsystems that could benefit from PID, then you can interleave all those PIDs and user control under a single while(true) loop.
This method of running multiple PIDs at once is great both for user control and high performance autonomous, but it requires more experienced programmer to do it right.
However, since @OK_BOOMER said in OP that they need PID for autonomous, then the code could be simplified by having PID function with its own while() loop that doesn’t return control back to the main program until it is finished.
@Anomalocaris has posted a very good code example that handles both integralActiveZone and integralPowerLimit (aka integral windup). Then if you add guards against division by zero as @mattjiang had suggested and max motor power cap guard per @thorstenl312 example, then you will have almost ready to use algorithm.
One thing I will add is that calculating of the derivative Kd can sometimes be noisy and, if that is the case you have an option to either not use it at all or employ some type of smoothing over multiple samples, or just increase sampling interval.
The remaining part of the puzzle for the case of autonomous, when drivers cannot interfere, is that you need your exit condition to be either abs(error)<threshold or timeRunningThisStep > maxAllotedTime for which you need to remember system time before starting PID loop and check current system time on every iteration with vex::timer::system().
https://api.vexcode.cloud/v5/html/classvex_1_1timer.html#aa9e59b87a0bb93463e1e8d580cb7bea6
Finally, the original @OK_BOOMER’s code had special handling for driving straight vs turning, which is very important to have for good autonomous driving PID. This had been discussed in the past and I am going to link several threads with good examples.
This is an older RobotC example, but is still good for reference:
In general, once you start feeling comfortable with using multiple interleaving PID loops at once, you will want to have one set of PID coefficients to control robot moving forward and another to control it heading.