[call page] tutorial by EonDog

Have something important that other people might find usefull? Well, drop it here and help others!
User avatar
LucidStew
Level 3
Level 3
Posts: 176

[call page] tutorial by EonDog

Post#1 » Mon Aug 15, 2016 6:20 pm

moved over from projectspark.com forums

[call page] provides a useful way to organize Kode. However, the tile is very easy to misuse, leading to skipped calls--which are difficult to diagnose (PS used to even crash when [call page] was used in certain ways, but as of update 10, this is no longer the case). Fortunately, I've navigated all the obstacles and managed to find a way to make the tile consistently useful. Below are my findings.

Why [call page]?
With [call page], you can create functions: pages which perform some work and then return. With functions, you can create object brains that specialize in a purpose (displaying text, perhaps), and relieve the responsibility for this purpose from other brains. It's a means of reducing duplication between your brains, which can help keep them simpler and easier to maintain.

Now, there are multiple ways to use [call page], but they aren't equally reliable.

Note: Want a way to conduct experiments and debug them efficiently? Try Test Lab, where all the findings in this post were developed and verified.

The Safest, Simplest Method
Using [call page] to Invoke Kode in Another Object
What follows is the safest approach I have found for using [call page] between objects. (To learn about the pitfalls of other methods, see section "Where Dragons Be" below.) The method is simple and has very few drawbacks. First I'll list the Kode (using LadylexUK's shorthand notation), then explain it.

First this, which needs only occur once (per object):

Code:
?
1
2
WHEN [not][brainVar: utility brain] DO [brainVar: utility brain] = [add brain][IWP: Utility Brain]
WHEN DO [added brain][switch page][0][slot][added brain][slot name]

Then, whenever you want to call a page:

Code:
?
1
WHEN DO [call page][text: @Function Page][from][brainVar: utility brain]

The first line checks a brain variable "utility brain" to see whether it holds a valid brain. If not, it adds a brain from an object chosen with the IWP (in-world picker). The second line, a child of the first, switches the page of the newly added brain to 0 (a non-existent page), ensuring the brain won't actually execute on its own each frame. It's a good practice to have these statements executed near the top of the object's main brain starting page.

Note: You can switch the added brain to any page and the calls will still work. Use page 0 if you don't want the added brain to run anything. Don't deactivate the brain at this point, as it will then block the calls.

The last line calls the page titled "@Function Page" in that brain. As long as this set of statements have executed in order in the object, and as long as the page actually exists in the given brain, this line will work. Specifically, it will work in any brain in or added to [me], and it will work on pages that are themselves the target of a [call page]. It will also enable the Simple Form of [call page] described below, requiring no modifiers.

WARNING: If the brain variable has not been assigned, then the call will fail to work (it used to even crash Project Spark, but as of Title Update 10, it is now just ignored). So make sure the assignment can't be skipped.

WARNING: Do not use [get brain] instead of [add brain] to get the brain from another object. See the Dragons below for explanation.

Simple Form: Calling a Page Within the Same Brain
As long as you follow the guidance above, the following notation will always be able to call a page within the same brain:

Code:
?
1
WHEN DO [call page][text: Name of Page]


I'll call this the Simple Form. With the Simple Form, you simply expect that the calling page and the called page are both in the same brain. Again: this only works reliably if you apply the "safe" approach above for calling pages in other objects.

NOTE: Following a convention begun by others, I name my pages with a "@" prefix, so that they are easily distinguishable from other text-tiles. For example, [call page][@Display Message] is a common occurrence in my creations.

Review In Brief
Just to make it as clear as possible, let's review the above method.

Use this whenever you want to call a page in another brain or object (after making sure the brain variable points to the added brain):

Code:
?
1
WHEN DO [call page][text: @Function Page][from][brainVar: utility brain]

Use this whenever you want to call a page in the same brain:

Code:
?
1
WHEN DO [call page][text: Name of Page]


Life Inside a Called Page
There are a few tile behaviors that might surprise you once you are executing Kode in a called page, so I want to shed some light on them.

A quick refresher on brain theory
Project Spark runs at 30 frames per second (30 FPS), which means that it updates the game one frame at a time, 30 times per second. Each frame represents 1/30 of a second of game activity, and this activity is defined by the objects and brains of your creation. During each frame, Project Spark visits every active brain, and executes the Kode in one selected page in that brain. I call this page the "active page", but project spark refers to it as [current page]. Either way, every active brain only has one such page at a time.

[This Brain], [current page] and related values
Now to the point: Before executing the active page for each brain, Project Spark assigns a value to [me] and [this brain], to point at the object and brain to which the active page belongs.

The values of [me] and of [this brain] never change while the brain is executing during that frame. You might be executing Kode in an added brain in an object half-way across the world because a [call page] brought you there, but [this brain] still points at the same active brain that Project Spark scheduled for execution during the frame. That means you can't always count on [me] or [this brain] to be at all related to the page the tile sits on. If the page is the target of a [call page], it won't be. It also means that the called page gets access to exactly the same variables as the calling page and no more. In particular the called page won't have any implicit access to variables in the called object or the called brain.

[current page] is similar: it always corresponds to the one active page in [this brain] for the frame. Likewise, [next page] and [previous page] follow from [current page]. None of these values change just because you are inside a [call page] call, they can only change as a result of [switch page] (and even then they don't change until the next time the brain has a chance to execute). So contrary to naming, [current page] is not always literally the "current page" (which is why I choose to call it the "active page" instead).

Where Dragons Be
Above, I showed a safe (and clean) method of using [call page]. There are other methods, though, and I'll explain those here so that you can decide for yourself which is best for your situation. But you are welcome to stop reading here and just adopt the safe approach.

Calling a page on an object
This is probably the simplest approach, and I really do wish it worked consistently:

Code:
?
1
WHEN DO [call page][text: @Utility Function][from][IWP: Logic Brain]


The good news: if this line is executed in the active page of the main brain in an object, and if the target page calls no pages of its own, then this line will reliably call the "@Utility Function" page in the selected object's brain and it has a chance to work.

The bad news: That is one enormous IF.

Problem 1: If [this brain] is actually an added brain in a multi-brain object, then Project Spark will attempt to call the "@Utility Function" page in a brain in the same slot and channel as [this brain]. More likely than not, there is no brain in that slot, and the [call page] will simply be skipped without warning. (I should point out that added brains are never in the default slot, which is why this is always a problem in added brains)

You can actually use the following notation to work around it:

Code:
?
1
WHEN DO [call page][text: @Utility Function][from][IWP: Logic Brain][in slot][default brain slot][in channel][default brain channel]


Of course, this turns a 4 tile problem into an 8 tile solution, and it still doesn't cure the other problems below.

Problem 2: If the target page performs a nested call within itself using the Simple Form, it looks for the page in [this brain], not in the target object.

For example, let's suppose that page "@Utility Function" in the Logic Brain object has this line:

Code:
?
1
WHEN DO [call page][@Internal Helper]


You would likely expect that this will find the "@Internal Helper" page in the Logic Brain object, but Project Spark will instead look in [this brain]: the active brain for the object. If that brain is anything but the Logic Brain's brain itself, the call will quietly fail.

It is theoretically possible to work around Problem 2 in the same way as with Problem 1:

Code:
?
1
WHEN DO [call page][text: @Internal Helper][from][IWP: Logic Brain][in slot][default brain slot][in channel][default brain channel]


This used to crash the game, but this has been fixed in a recent update. Now, it simply doesn't work in nested scenarios. Of course, even if it did, this approach would turn a 2 tile problem into an 8 tile solution.

The solution I recommend at the outset of this post suffers none of the above problems.

Calling a page on an object brain using [get brain]
One final technique is a bit of a hybrid of the other approaches.

Code:
?
1
WHEN DO [call page][text: @Function Page][from] [(] [get brain][IWP: Utility Brain] [)]


In this case, you can put the result of the [get brain] call in a temporary or even permanent variable (in a similar manner to the recommended technique) to shorten the statement, or just use the call as is.

The main benefit this technique holds over the recommended one is that you don't have to worry about adding a brain and making sure it is never removed or on the wrong page. Another benefit is that [get brain] retrieves a brain from the default slot and channel by default, so it will work the same in both default and added brains. However, in every other regard it has the same drawbacks as calling a page on an object: nested calls cannot use Simple Form (because the target page is found in [this brain] by default), and it requires a lot of tiles.

Conclusion
[call page] is a useful function, but it's hampered by a number of obstacles. Hopefully, the recommendations here will allow you to get the most out of it, at least until these obstacles are removed.

Good luck!
--
John (EonDog)
End of line.

Return to “Tutorials and guides”

Who is online

Users browsing this forum: No registered users and 1 guest

cron