18,700
edits
m (finished latest pass at wording) |
(elaboration on recursion and forking; brought over warning about dprint/dmsg from BSL:Functions) |
||
Line 347: | Line 347: | ||
|} | |} | ||
To | To illustrate the '!' ("not") operator, the following three lines are equivalent: | ||
!(some_bool eq true) | !(some_bool eq true) | ||
some_bool eq false | some_bool eq false | ||
Line 605: | Line 605: | ||
===Recursive calling=== | ===Recursive calling=== | ||
A function can call itself | A function can call itself by invoking its own name: | ||
var int global_iterator = 0; | |||
func void bad_loop_idea(void) | |||
{ | |||
global_iterator = global_iterator + 1 | |||
dmsg("Calling myself..."); | |||
bad_loop_idea(); | |||
} | |||
This might seem useful for producing a loop, but a function can only call itself recursively about four times before you hit a limit in BSL and the script fails silently. Open a level with the above code placed in its level script and call bad_loop_idea() from the dev console. After you stop seeing the "Calling myself..." messages, run the command "global_iterator" to query its value and you'll probably get "5", meaning the recursion failed between the fourth and fifth levels. | |||
This limit can be bypassed if you use "[[#fork|fork]]" when calling a function from within itself, though this is not technically recursing: the logic will not complete in a predictable inside-to-outside order like our nested parentheses example in the previous section; instead it will run concurrently through all of the forked function calls. This can be very bad if you're trying to get reliable results, however there are ways to mitigate this. For a discussion of looping reliably with "fork", see {{SectionLink||Looping}}. | |||
===Returning=== | ===Returning=== | ||
As mentioned under {{SectionLink||Flow interrupt}}, "return" | As mentioned under {{SectionLink||Flow interrupt}}, "return" allows you to exit the function at that point. But because of the bug documented in that section you cannot use "return" conditionally, that is, exit early from a function depending on some logical condition ("if"). Since "return" can thus only be placed at the end of a function, there is no need to use it at all unless you are passing back a value with it like so: | ||
func int add_ten(int input) | func int add_ten(int input) | ||
Line 616: | Line 628: | ||
} | } | ||
When a function returns a value, you can use the function to set a variable as follows: | |||
some_number = add_ten(enemy_count); | some_number = add_ten(enemy_count); | ||
Function return values can also be used in "if" statements | Function return values can also be used in "if" statements. If you have a function that returns a bool... | ||
func bool is_it_safe(void) | func bool is_it_safe(void) | ||
Line 635: | Line 647: | ||
else | else | ||
dprint("It is not safe."); | dprint("It is not safe."); | ||
Bizarre bug warning: Calling dprint() and dmsg() more than once within a function will produce unexpected results. One of the dprint()/dmsg() calls may become the return value for the function (even if it it's type "void"), or create a return value of 0, and more than two calls also might be ignored. | |||
===Looping=== | ===Looping=== | ||
Line 654: | Line 668: | ||
} | } | ||
Note that calling the same function again before it finishes running the first time can have undesired effects. Using "sleep" before each call can prevent this overlapping execution. A short function like the example above should not need a "sleep" statement | Note that calling the same function again before it finishes running the first time can have undesired effects. Imagine if you have another thread in the code accessing "counter" while increment() is still running in a loop; what value will it have at that moment? Real-world scripting scenarios will be more complex and thus more problematic if multiple concurrent calls to a function are in play at the same time. Using "sleep" before each fork call can prevent this overlapping execution. A short function like the example above should not need a "sleep" statement as the remainder of the function after the "fork" call will complete in the same tick. But the more complex the function, the more ticks you will need to allow for it to complete, requiring "sleep(1);", "sleep(3);", etc. before the "fork" call. | ||
An alternate looping method: | An alternate looping method: See the two variants of the "schedule" keyword under "Concurrency" below. | ||
===Concurrency=== | ===Concurrency=== | ||
Line 685: | Line 699: | ||
As you can see, fork-called functions can delay actions within themselves without holding up the rest of the script; this not only allows the use of "sleep" without affecting outside functions, but also any built-in functions that hold up BSL by design, like [[chr_wait_animation]]. | As you can see, fork-called functions can delay actions within themselves without holding up the rest of the script; this not only allows the use of "sleep" without affecting outside functions, but also any built-in functions that hold up BSL by design, like [[chr_wait_animation]]. | ||
Be aware that accidentally calling a nonexistent function using "fork" will crash Oni; when "fork" is not used, BSL will successfully recognize | Be aware that accidentally calling a nonexistent function using "fork" will crash Oni; when "fork" is not used, BSL will successfully recognize it as an unknown function and thus will not attempt to call it. | ||
====schedule ... at ...==== | ====schedule ... at ...==== |