In this tutorial I'll go through some simple techniques you can use to set up timeout failsafes, which you will definitely need for your scripts to deal with expected and unexpected failures correctly.
What is a timeout failsafe?
It's my term anyway, but the idea is universal. When you do something, chances are you don't know whether you have succeeded or not. But one thing you know for sure is you have have tried it for X amount of time and have yet to succeed, it's very safe to assume that something is wrong.
Timeouts are used everywhere in computers. From web pages to server interactions, your favourite PC/console multiplayer games, etc. So why don't we use this idea well in scripting as well?
I'll show a number of ways to set up timeout failsafes.
The looping method
The looping method involves trapping you what want to try inside a loop. The loop is set to expire after time T or a number of trials N. If an attempt is successful however, you will break out from the loop and continue your next step in execution.
The looping method can be done with all loops that can be constructed in pascal. That said, repeat... until, for... to... do, while...do loops, they all work. I will show some examples.
To wait for the bankscreen to appear, that's what I did in my first script.
Simba Code:
MarkTime(t);
repeat
wait(200+random(20));
until (BankScreen or (TimeFromMark(t) > 10000));
if (TimeFromMark(t) > 10000) then Exit;
;//more code here
That above code says, if the bankscreen is found within 10 seconds, break out from the loop and proceed to next step (deposit, withdraw). If we break out from the loop but we passed the 10secs timeout, something must be wrong. Exit the function and return false
Actually it's as easy as that. I generally like the use of repeat... until because it is equivalent to a do-while loop, which means you at least attempt whatever you need to check at least once. but while...do and for...to...do works exactly the same, just different coding style.
To illustrate, let's make up something to see it in action
Simba Code:
for x:= 0 to 9 do
begin
wait(100);
if BankScreen then break;
end;
if BankScreen then
;//do banking
This has a similar effect by waiting 100ms before asking whether we're in the bank screen for 10 times. If we are already in bankscreen, it will break and it will execute the banking stuff. If after 10 times we still cannot find the bankscreen, we'll have essentially failed and it will not bank the stuff.
The WaitFunc and WaitFuncEx method
For people who doesn't enjoy many levels of nesting with repeat...until blocks, another good option is to use the SRL include WaitFunc and WaitFuncEx functions. These functions can be used to implement timeout failsafes as well. Good thing about it is that they look really neat to me, so it's really worth trying.
Simba Code:
function WaitFunc(Func: Function: Boolean; WaitPerLoop, MaxTime: Integer): Boolean;
{Waits for function Func to be true. WaitPerLoop is how often you
want to call "Func" function.
Example: "WaitFunc(@BankScreen, 10 + Random(15), 750);" will check if BankScreen
is open every 10-25th millisecond, for a maximum of 750 milliseconds.
Notice the '@'. }
Basically WaitFunc can use any function that takes no arguments and returns a boolean and wait until timeout or the function returns true. A great example of using this would be to check whether the Bankscreen is opened after we interact with the banker.
Simba Code:
function DoBanking: boolean;
begin
Result:=False;
if not WaitFunc(@BankScreen, 100, 3000) then Exit;
DepositAll;
Result:=True;
end;
Basically it's exactly the same as the loop method but you can get a similar effect in one line. Neat isn't it?
Note with WaitFunc (or WaitFuncEx) you can only implement a timeout block on the true condition. If you need to implement a timeout on the false condition, you need a wrapper. Let's say we're silly enough to do the exact opposite of the above. We would then have to do:
Simba Code:
function NotBankScreen: boolean;
begin
Result:= not BankScreen;
end;
function SillyBanking: boolean;
begin
Result:=False;
if not WaitFunc(@NotBankScreen, 100, 3000) then Exit;
DepositAll;
Result:=True;
end;
Notice how we have created a function that wraps the result we want to a true condition, and call it in our SillyBanking function.
Now we move onto the beastly WaitFuncEx function. SRL says:
Simba Code:
function WaitFuncEx(Func: string; var Args: TVariantArray; WaitPerLoop, MaxWait: integer): boolean;
{Calls Func with arguments Args every WaitPerLoop milliseconds for a max of
MaxWait milliseconds. Func can return any basic types (boolean, string,
integer). Boolean: Returns it. String: Returns true if string equals 'true'.
Integer: Returns true unless it e
.. note::
by Dgby714 }
WaitFuncEx is more complicated, but gives even more flexibility by allowing us to specify an array of arguments to pass to the function that we are going to wait for. It accepts the function name as string, an array containing the arguments to which we want to call, and the usual timing arguments.
Actually WaitFuncEx is quite flexible that it can accept functions returning non-boolean values. It's a nice touch, but I don't use that flexibility at all. I just keep it for checking boolean returns. For timeout failsafes, boolean return values are usually all you need.
Onto actual example. To use this function we will have to declare a TVariantArray and set the arguments in the TVariantArray before calling WaitFuncEx. Let's take a look at an example:
Simba Code:
function InGrandExchange: boolean;
var trash: integer;
v: TVariantArray;
begin
v:=[trash, trash, 'Grand', 'UpCharsEx', MSX1, MSY1, MSX2, MSY2];
Result:= WaitFuncEx('FindText', v, 200, 3000);
end;
The above shows a function that tells whether we're in Grand Exchange screen with timeout implemented. Silly but fairly solid.
Note if you need to call waitFuncEx multiple times, you'll have to set the contents of the TVariantArray everytime before you call waitFuncEx.
An example below:
Simba Code:
function BogusCode: boolean;
var trash: integer;
v: TVariantArray;
begin
v:=[1, 2, 3, 4];
if WaitFuncEx('whateverfunction', v, 200, 3000) then
begin
v:=['a', 'b', 'c', 'd'];
if WaitFuncEx('anotherfunction', v, 200, 3000) then
;//proceed
end;
end;
So that's about it! You should now know how to do timeout failsafes in various manners. Pick whatever you're comfortable with. Feedback is much welcomed.