BSL:Manual: Difference between revisions

1,539 bytes added ,  14 December 2023
elaboration on recursion and forking; brought over warning about dprint/dmsg from BSL:Functions
m (this sample code was always cursed; should be more logical now)
(elaboration on recursion and forking; brought over warning about dprint/dmsg from BSL:Functions)
 
(One intermediate revision by the same user not shown)
Line 347: Line 347:
|}
|}


To elaborate on the purpose of '!' meaning "not", the following three lines are equivalent:
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 558: Line 558:


===Defining===
===Defining===
When beginning to define a function, you must begin the line with "func"...
When beginning to define a function, you must start the line with "func"...


  func int prepare_fight(int count, string enemy_name)
  func int prepare_fight(int count, string enemy_name)
Line 586: Line 586:


===Calling===
===Calling===
When you call a function it will execute its statements from top to bottom or until it hits a "[[#Returning|return]]" statement. This means that when you call a second function from inside another function, the second function will be executed until its end and then the rest of the first function will be executed.
When you call a function it will execute its statements from top to bottom or until it hits a "[[#Returning|return]]" statement. This means that when you call a second function from inside another function, the second function will be executed until its end and then the rest of the first function will be executed. You can think of the function calls as nested parentheses, where the innermost statement must close before the next-innermost level can close, and so on. (For example, here is a parenthetical statement (and another one (and one more!) inside of it)).


You do not use "func" when calling a function. You simply use its name:
You do not use "func" when calling a function. You simply use its name:
Line 605: Line 605:


===Recursive calling===
===Recursive calling===
A function can call itself, which is useful for various looping behavior, but a function can only call itself recursively up to about four times. Note that this limit is bypassed if you use "[[#fork|fork]]" to call a function from within itself, but this no longer counts as recursing because the logic will not complete in a predictable inside-to-outside order. For a non-recursive way to loop a function, see {{SectionLink||Looping}}.
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" exits the function at that point. Because of the bug documented in that section, you cannot exit early from a function under some logical condition. Since "return" can thus only be placed at the end of a function, there is no point in using it at all unless you are going to pass back a value by placing a variable name after "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:
  }
  }


You would then use this function to set a variable, as follows:
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 to provide a true/false condition. If you have a function that returns a bool...
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 631: Line 643:
...then it could be called this way in some other function:
...then it could be called this way in some other function:


  if (is_it_safe())
  if (is_it_safe() eq true)
     dprint("It is safe.");
     dprint("It is safe.");
  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, 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.
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: See the two variants of the "schedule" keyword under "Concurrency" below.


===Concurrency===
===Concurrency===
Line 681: Line 697:
  }
  }


As you can see, you can use forked functions to perform delayed actions without holding up the rest of the script; this not only allows the use of "sleep", 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]].
 
Also be aware that accidentally calling a nonexistent function using "fork" will crash Oni; when "fork" is not used, BSL will not recognize that the unknown function name represents a function, and thus will not attempt to call it and end up crashing.


Below are two types of uses for the "schedule" keyword. By scheduling a function call that operates on global variables and doesn't run unless a condition is true, you could simulate a slow-acting loop with these keyword sets.
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 ...====
Line 704: Line 718:
  schedule dprint("Is this annoying yet?") repeat 50 every 20;
  schedule dprint("Is this annoying yet?") repeat 50 every 20;


This is equivalent to the following loop in a C-style programming language:
This is equivalent to the following loop in a C-style language:


  int i = 0;
  int i = 0;
Line 718: Line 732:
  schedule dprint("Is this annoying yet?") repeat 0 every 20; # repeats forever
  schedule dprint("Is this annoying yet?") repeat 0 every 20; # repeats forever


"schedule-repeat-every" can be used in place of recursive functions that call themselves ''n'' times using "fork". For example,
"schedule-repeat-every" can be used in place of recursive functions that call themselves ''n'' times using "fork". For example…


  func void hey(void)
  func void hey(void)
Line 732: Line 746:
  }
  }


can be replaced by:
…can be replaced by…


  func void hey(void)
  func void hey(void)