Jump to content

Blocking API - a new UI library for Wherigo


matejcik

Recommended Posts

Fellow programmers, geocachers, Wherigo creators,

 

me and some other guys are playing around with this new concept of doing things in Wherigo. It looks like this:

 

cart-bear.gif

 

Most of you would be able to create this sort of interaction without much trouble, right?

Well, have a look at the Lua code underneath it.

 

function zcharBear:OnTalk()
 name = UI.Input {zcharBear, "Hi, what's your name?"}
 UI.Message {zcharBear, "Well, "..name..", I'm hungry."}
 UI.Message {Player, "Um ... okay, that's ... i'll just ..."}
 response = UI.Choice {zcharBear, "Do you have anything to eat?", Options={"Sure", "I'm afraid not"}}
 if response == "Sure" then
   UI.Message {zcharBear, "Gimme!"}
 else
   UI.Message {zcharBear, "Hmm, too bad. Now I'll have to eat you!"}
 end
end

You will notice that there are no callbacks, and that instead of creating a ZInput, we just get the response directly and use it right away. Pretty cool, huh?

 

Disclaimer 1: This is only relevant for those of you who write cartridges (at least partially) by hand. There is no builder support.

 

Disclaimer 2: What I'm posting here is closed alpha-release. It's alpha, meaning there are bugs, and it's closed, meaning that i won't post the code here - for now anyway.

 

That said, the code exists and is readily usable, in the form of an author script. I'd like to hear your opinions on the new API model, and possibly your experiences with programming with it.

 

Q: But how can I get the code?

A: PM me if you're interested. I'll send it to you - if i judge you worthy /mwa ha ha/. No, seriously, just ask.

 

Q: Then why don't you just post the code here?

A: Because I don't want everyone and their grandma to include it in their cartridges before we manage to iron out the kinks.

Here's a little secret: something very much like this will end up as part of OpenWIG, WhereYouGo and other modern players. We're trying to design the API right, so we are showing it to you early - but if everyone started using this version now, we would have a compatibility nightmare on our hands.

For this reason, I will only send the code to people who have been active on the forums, and i'll want you to promise that you won't give the code to anyone else before it is ready.

 

Q: Ha ha, I bet I could write something like this myself.

A: Really? Cool. Why don't you join the discussion here and share your ideas about how it's supposed to work?

 

Q: Okay, I got the code. What now?

A: You already got the "installation instructions" in your PM. The next post in this thread contains a short programmer's guide.

 

Have fun!

Link to comment

Programmer's Guide

 

1. Interaction model

 

Unlike traditional Wherigo API (which i will call legacy API from now on), in the blocking API, there is an implicit message queue. That means that if you send a message to the screen before the user is done with the previous one, nothing bad happens. When the user clicks OK on the first message, your next message will be shown.

 

Also unlike the legacy API, some of our functions are blocking. In legacy API, you would do this:

Wherigo.MessageBox{Text="start timer"}
ztimer:Start()

and the timer would start immediately - before you finished reading the message text. With blocking API, when you do this:

UI.Confirm{nil, "Start timer?"}
ztimer:Start()

the timer will start only after the user clicks OK. In other words, Confirm will block program execution until it is finished.

 

You may be thinking now, "What about timer events? What about zone events? Will they be stuck waiting too?" And the answer is no, they won't. Let's say you have two zones, zone A and zone B:

 

function zoneA:OnEnter()
 zoneB.Active = true
 UI.Confirm{nil, "you are in zone A"}
end

function zoneB:OnEnter()
 zoneC.Active = true
 UI.Confirm{nil, "you are in zone B"}
end

When you enter zone A, zone B will become active and you will see a messagebox. Let's say that you don't click OK and keep it on the screen. When you walk into zone B, zone C will become active (that's the first line), and then the Confirm call will block until you click OK on both the first and the second messagebox.

 

You: (walk into zone A)

Cartridge: "you are in zone A"

You: (walk into zone B)

<--- at this point, there are two events "running" (they're suspended, actually) at the same time

You: (click OK on "you are in zone A")

<--- the first OnEnter is unblocked and finishes. only one event is "running" now.

Cartridge: "you are in zone B"

You: (click OK on "you are in zone B")

<--- the second OnEnter can finish

 

To make this possible, a third property of the blocking API is necessary: the same event cannot run twice at the same time. That means that, in the previous example, if you left zone A and returned to it before clicking OK, the OnEnter would not run again.

 

2. API Overview

 

There are seven functions available:

UI.Message, UI.Confirm, UI.Choice, UI.Input, UI.Notify, UI.Cancel, UI.Wait

You cannot use these functions and traditional Wherigo functions (MessageBox, Dialog, GetInput) at the same time. Well, you can, but if you do it, everything will break horribly. Stick to the one or the other.

 

First let's look at UI.Message in detail. There are several ways to call it:

UI.Message { Sender=zObject, Text="some text", Media=zmediaSomeMedia }
UI.Message { zObject, "some text", zmediaSomeMedia }
UI.Message { zObject, "some text"}

Notice that it's "UI.Message { ... }". It won't work with regular parentheses.

(the function signature is actually UI.Message(table). Lua allows you to call UI.Message {something} instead of UI.Message({something}) in this case.)

 

The Sender argument can be a Zone, ZCharacter, ZItem, ZTask or even ZCartridge. In the first example with the bear, I've been using the Bear character as Sender. This is to emphasise that the messages are part of a conversation, and that someone or something is talking to you. Sender can also be Player (if it is something the user is saying), or nil if you want the message "from nobody".

At this point, Sender is only used for the picture, but it just might do something interesting in new versions of the player apps.

Text is obvious. Media, when specified, overrides Sender's picture. So if you want to show many pictures and you don't have characters to represent them, just use Sender=nil and Media=yourPicture.

 

All functions, except Cancel and Wait, take these first three arguments, but they can usually have more.

 

All functions return nil when canceled (more about that later). Where not specified, the function returns true when not canceled.

 

3. Functions

 

UI.Message

This function is non-blocking. That means that, in the following code, timer will be started immediately, without waiting for the user:

UI.Message{nil, "start timer"}
timer:Start()

The messages from UI.Message are still queued though. Look at the bear example to see them in action.

 

UI.Message is the "normal" message that you would be using most of the time.

 

UI.Confirm

Counterpart of Message that is blocking. Useful for waiting before starting timer, activating a zone, whatever.

This one has an optional argument, Button, that lets you specify button text.

UI.Confirm{nil, "start timer?", Button="start"}
timer:Start()

 

UI.Choice

Multiple-choice question. Similar to ZInput of type MultipleChoice. Has an argument Options that specifies the available options.

Blocks until the user answers, returns text and index of the option:

text, index = UI.Choice{zcharBear, "What kind of food do you have?", Options={"fish", "steak", "vegetables"}}
-- text == "steak", index == 2
text = UI.Choice{zcharBear, "What kind of food do you have?", Options={"fish", "steak", "vegetables"}}
-- text == "steak"

 

UI.Input

Text input - similar to ZInput of type Text. Has no more arguments. Blocks until the user answers, then returns the answer text.

 

UI.Cancel

Cancels everything on screen, and removes everything from the event queue. Blocks until processing is done.

Simply put, you call Cancel to clear the screen for you - as if the user started clicking OK on everything in rapid succession, until nothing remained.

(Unfortunately, on legacy players, there is no function to remove a message or input from screen. so instead, it is replaced by a messagebox that says "Canceled." But this one is not queued - if you call UI.Message immediately after UI.Cancel, the users will see your message.)

 

A small example looks like this:

function item:OnAskQuestion()
 UI.Confirm{zcharBear, "you have 10 seconds to answer me"}
 timer:Start()
 reply, idx = UI.Choice{zcharBear, "how many heads does a turtle have?", Options={"three", "lemon", "tuesday"}}
 timer:Stop()
 if idx == 3 then
   UI.Message{zcharBear, "that's correct!"}
 else
   UI.Message{zcharBear, "sorry, that is wrong"}
 end
 zoneCave.Active = false
end

function timer:OnTick()
 UI.Cancel()
 UI.Message{charBear, "too late! now i eat you!"}
end

What's going on here? After clicking OK on the Confirm, a timer is started. Then the bear asks you a multiple-choice question, and the OnAskQuestion handler is suspended until you answer.

If you answer within 10 seconds, timer is stopped and the bear will tell you whether you were right or wrong.

If, however, you wait too long, timer's OnTick will fire and call UI.Cancel. At that moment, every UI operation from the suspended OnAskQuestion is considered canceled. We can't just kill the event (well we could but that would be stupid), so we let it finish - but the blocked UI.Choice call will return immediately, and every following UI call will simply be skipped.

 

Let's do it step by step.

OnAskQuestion: "reply, idx = UI.Choice{zcharBear, "how many heads does a turtle have?", Options={"three", "lemon", "tuesday"}}"

Screen: "how many heads does a turtle have?"

<--- OnAskQuestion is now suspended and waiting for user reply

<--- 10 seconds pass

OnTick: "UI.Cancel"

Screen: "canceled"

<--- OnTick is suspended, waiting until queued events are finished.

<--- OnAskQuestion is resumed. reply and idx are nil

OnAskQuestion: "timer:Stop()"

<--- nothing happens - the timer is already stopped

OnAskQuestion: "UI.Message{zcharBear, "sorry, that is wrong"}"

<--- nothing happens - OnAskQuestion is canceled, so all UI operations are ignored.

OnAskQuestion: "zoneCave.Active = false"

<--- zoneCave is deactivated. This still happens, even though the event was canceled.

OnAskQuestion: done

<--- all queued events are done. OnTick is resumed

OnTick: "UI.Message{charBear, "too late! now i eat you!"}"

Screen: "too late! now i eat you!"

 

UI.Wait

Takes no arguments, does nothing, but blocks until the screen is clear (or until canceled)

I can't come up with an example where this function would be useful. But maybe some exist, and it was easy to implement, so we left it in.

 

UI.Notify

Displays a notification message which is not queued. Does not block. The message is displayed immediately, and if you try to display anything after it, the notification will disappear.

This is similar to how the original Wherigo.MessageBox works. But with one key difference: when you click OK on a Notify message, you go back to where you were before.

 

Let's go back to the example from UI.Cancel. We will extend the time limit to 30 seconds, and add a second timer:

annoyingTimer = ZTimer{Type="interval", Duration=5}

function timer:OnStart()
 annoyingTimer:Start()
end
function timer:OnStop()
 annoyingTimer:Stop()
end

function annyoingTimer:OnTick()
 UI.Notify{nil, "you have " ..tostring(timer.Remaining).." seconds remaining"}
end

That's it. During the time the multiple-choice input is shown, you will get a message every 5 seconds.

If you click OK on the notification, you go back to the multiple-choice.

If you don't, another notification will replace it, showing the new time.

 

Note 1: Due to the way this is implemented, if a Notify box covers an Input, whatever you typed into the input will be lost. I suspect that this will be especially annoying on Garmins. So don't do it.

 

Note 2: If you show a Notify box, then the first UI action that would be shown will make it go away. The result of this rule is a bit weird: If you show a Notify on an empty screen, then the next UI.Message will overwrite it. But if you show Notify on top of a waiting Confirm or something like that, then all other UI events will go to the queue, and the notification will stay visible until the user clicks OK - or until another notification (which would be shown immediately) clears it.

Or, obviously, until it is canceled.

Edited by matejcik
Link to comment

posted the first part of the programmer's guide

 

What happens, if another event throws a UI.Message for the interface (you enter the zone). Could I confide, that I get the result from the input?

yes, the input will stay on screen until the user is done with it. a message from another event will be queued.

what can happen, though, is that after the input, another message will be displayed before that "I'm hungry" one. I'm not sure whether that's a good thing. we will see.

Link to comment

just posting this here, that should force me to keep the schedule :e)

 

more than a year after posting this, I'm close to finishing a bug-free version of the code. In the upcoming weeks, I will probably publish the code on here for the Public Beta.

 

Looking forward to taking this out for a spin.

Link to comment

just posting this here, that should force me to keep the schedule :e)

 

more than a year after posting this, I'm close to finishing a bug-free version of the code. In the upcoming weeks, I will probably publish the code on here for the Public Beta.

 

Hi matejcik,

 

are you seeing any progress with the Blocking API?

I hope there haven't appeared any major obstacles during development?

 

Is there any chance to get a bug-free (well, as bug-free as SW goes...;) ) version within the next few weeks?

I am also really looking forward to play around with the new lib!

 

If you think you could use some outside support in any way, please feel free to ask!

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
×
×
  • Create New...