This thread is for small questions about Lil, the Decker scripting language. If you have a more involved question, feel free to start your own thread. If we accumulate enough information for an FAQ I'll edit this post to include a link.
The Lil manual can be found here.
Lil performance question: I have used LiveCode for years, and its built-in language is not fast. So when I try out a new language, the first thing I do after Hello World is a for loop to get a sense of the speed of the language. I tried that with Lil on an M1 MacBook, and found that
on click do each x in range 1000000 end alert["Done!"] end
takes about 2 seconds to run. This is about 30x slower than even LiveCode. This is not a criticism, just a question: any thoughts on ways to speed up Lil?
There's a lot of room for improvement in Lil performance. The each loop in your example is particularly expensive because "each" is a map operation yielding a result list, range eagerly creates an entire list, and each loop body is executed in its own scope. Contrast with "while", which is considerably faster due to involving less bookkeeping and keeping much less in memory at once:
while x<1000000 x:1+x end
Or, when it's possible, just using the natural "conforming" over lists:
1+range 1000000
Performance for each over a huge list is particularly bad in c-lil; I'll do some investigating.
Interesting -- I'm not terribly familiar with list generation: I've played with Python, so I know it's a thing, but LiveCode has nothing like it built in, and I would never do something like
repeat with i = 1 to 1000000 put i,"" after aList end repeat repeat for each item i in aList -- do something end repeat
I just checked and found that the while loop was even slower :-)
I'm not sure this accomplishes the same task, albeit that the task is synthetic in the first place, but in any case, this is about 20x faster:
on click do range 10000000 alert["Done!"] end
Oh, there's one other thing I should note that's probably making us talk past one another somewhat: Lil in Decker is deliberately capped at a specific number of "ops" (VM bytecode steps) per "frame" (display updates at 60fps) . This is an arbitrary creative constraint intended to help decks behave more consistently across different machines with wildly different levels of performance. The "FRAME_QUOTA" constant in the JavaScript implementation controls this cutoff.
Oh, HA! Okay, that makes sense. "constant in the JavaScript implementation" -- meaning updating it requires rebuilding Decker from source? Or is it configurable?
You can rebuild from source, or, in a web build, just open the file in your favorite text editor, search for "FRAME_QUOTA", and tweak as desired. I may raise the limit and/or make it configurable at runtime in the future.
Is there an API to check “fraction of script steps remaining this frame” from inside a Lil script? (For operations with shorter limits, like accessing a property on a Contraption, is there one that checks the operation limit?)
I’m consider9ing Decker to write a loosely-chess-inspired roguelike, and I’d want to use this to know when to stop searching for a move on the current frame and advance a “thinking” animation so the deck does not act frozen during the opponent’s turn.
There is not presently a way to inspect the remaining per-frame quota (or other limits) from within executing scripts.
As a developer you can get a rough idea of how close your scripts are to quota by enabling the "Script Profiler" feature from within the Decker menu. This will display a live chart of the percentage of quota scripts have consumed over the past few seconds.
If you want to provide the user with visual feedback during long-running scripts, you need only draw visible updates from time to time. When a script runs out of quota it is simply paused momentarily to allow Decker to service input and then automatically resumed, unless the user explicitly halts it.
LiveCode script (and the GPLv3 community fork OpenXTalk) has arrays which can be considerably faster then using comma-seperated-values for text container 'lists' (but when I do I use tab as a delimiter).
Also LC / OXT has a second language, the Extension Builder lang for making 'Widgets' and wrapping external code libraries, which does have an actual List type where the ordered elements can be of any type, Text, Numbers, JSON, Java, or C and ObjC types, Pointers, etc.
What's a good way to get an image string? I was trying `read["image"]` in c-lil and I was only getting the contents of the file as a byte string.
so I tried to get it from js-lil, and I couldn’t manage to copy the string out of the listener. After copying it and pasting it in the script editor, the string appears as an image within the script..
for reference, the image string is
%%IMG2ACIAGCAuAQwgEgEEIAYBAiAEAQIgEAEEIAYBAiAEAQIgDgECIAgBAiAIAQIgDAECIAgBAiAIAQIgCgECIAoBAiAGAQIgAgECIAgBAiAKAQIgBgECIAIBAiAGAQIgDAECIAYBAiACAQIgBgECIAwBAiAGAQIgAgECIAYBCiAGAQIgCAECIAYBCiAGAQIgCAECIAQBAiAKAQIgBgEMIAIBAiAKAQIgBgEMIAIBAiAKAQIgBAECIAwBBCAKAQIgBAECIAwBAiACAQogBAECIAIBDCAEAQogBAECIAIBDCAGAQIgDAECIAoBAiAGAQIgDAECIAoBAiAIAQQgCgEKIAoBBCAKAQogDgEKIBgBCiAO
and this is a screenshot of what happens:
Pasting images in non-rich fields was a bug; this should be fixed now. Image strings are how images are represented in the clipboard, so you should now be able to make image-strings using normal drawing tools.
Lilt's read[] has a slightly different signature than Decker, since Decker prompts the user instead of taking a path argument; that might be what was tripping you up there.
What is a good way to order a query by two values? I’m making a flash-card deck and want to sort the cards based on some “score” I calculate, and if they’re equal fall back on the time they were last accessed. Tupling the values is not an option, since anything but an integer will be converted to string for comparison. I could just do that with nested queries, but that doesn’t feel very good.
Well, you could've formatted columns together to form keys with a natural lexicographic comparison, but that's horrible.
I made a tweak to the behavior of "orderby" which borrows from how the "grade" operators work in K: lists are now given a lexicographic comparison, so tupling will now work. The "join" operator will zip together columns for this purpose. As a contrived example:
e:insert c:""split"ABBBABABABABAAABA" n:(7,7,2,4,4,0,3,4,2,0,9,7,4,6,8,2,3) into 0 select c n orderby (c join n) asc from e +-----+---+ | c | n | +-----+---+ | "A" | 2 | | "A" | 3 | | "A" | 3 | | "A" | 4 | | "A" | 4 | | "A" | 6 | | "A" | 7 | | "A" | 8 | | "A" | 9 | | "B" | 0 | | "B" | 0 | | "B" | 2 | | "B" | 2 | | "B" | 4 | | "B" | 4 | | "B" | 7 | | "B" | 7 | +-----+---+
Would be cool if Decker had a discord server, or a channel on the Fantasy Consoles server
You could ask in the Fantasy Consoles server to add a channel?
Is there a good way of returning multiple values from a function? For now the best I have is to pack them in a list and manually unpack to variables. Something like Lua’s unpacking would be really handy, but it conflicts with the current syntax: a, b: 1, 2
is a list of a, 1, 2
and assignment of 1
to b
. In a world of pure functions it’s really hard to avoid multiple output.
Not really an answer to my question, but a solution to my problem: I found a way to store mutable state. Using closures it’s possible to write “constructors” that return “objects” which have persistent mutable state, and calling “methods” of the “object” will change its state for all references to the object. Here’s an example of a stack constructor I’m using in my current project:
on new_stack do
state: ()
("stack","push","peek","pop") dict
(on _ do state end
,on _ x do state[count state]: x end
,on _ do (-1 take state)[0] end
,on _ do ans:-1 take state state:-1 drop state ans[0] end
)
end
This method is used in the example module in the documentation, although outside the function so it only creates a single global mutable state.
Edit: A universal minimal mutable variable:
on new_var state do
("get","set") dict
(on _ do state end
,on _ new do state:new end
)
end
my question is kinda rudimentary in nature, so how do I make a sound play continuously in a card? I want it to be played after the button click sound ends(we arrive at card) and play until another button is interacted with, thanks in advance!
Waiting for all sounds to finish is straightforward: sleep["play"]
So, playing a sound during a transition, letting the sound finish, and then playing another might have a script like:
play["firstSound"] go[anotherCard "BoxIn"] sleep["play"] play["secondSound"]
Decker isn't really designed with looped audio in mind. Keep in mind that individual sounds in Decker are capped at 10 seconds, so they wouldn't work especially well for background music. You might be able to get continuous background noise by monitoring sys.ms and periodically issuing another play[], but it isn't straightforward. Perhaps a feature for the future...
as someone who procrastinates a lot, i'm building a timer in decker using a field and a button to both act as a focus tool and also to apply some newfound programming knowledge. i have the basic countdown, but i'm wondering how i can make the program delay a second before going through the while loop again. any help is appreciated!
on click do while display.text>0 display.text: display.text - 1 #delay goes here end end
You could use the sleep[] function to wait a given number of frames. "sleep[60]" would delay for approximately one second.
A different approach for timing (which could be more accurate over longer time periods) would be to use `sys.now` or `sys.ms` to record a starting time and then consult them periodically to determine how much time has passed, possibly using "sleep[]" to delay between checks. (See: the System interface)
Thank you to the both of you! This helps a lot.
sleep[]
looks a bit unsatisfying, as the card is not interactive as it waits, so the label isn’t that readable.
I have tried this:
Make a textfield to store a timer (here I used “timer”) initially set to 0. You can put it on another card if you want to hide it out the way.
Card:
on view do
timer.text:timer.text+1
if (timer.text<0) & ((10%timer.text)=0)
display.text:display.text - 1
end
go[card]
end
Button:
on click do
timer.text:(display.text+1)*-10
end
Edit: I was late to the party. Using sys.now as suggested above sounds like a more suitable approach :)
Using the same "view[] refreshing" strategy is definitely the best way to do something like this if you want to be able to interact with the rest of the card while the timer is running.
You can also make the "secret timer" field invisible if you want to hide it on the current card.
https://cptnqusr.itch.io/super-cool-timer
here's the finished timer! not exactly the greatest thing ever but as a first project i'm quite happy with it
i know that lil has fuse, but how would i do something like `{x,"\n",y}/("hello";"world";"etc")` in k? i have a list of strings of arbitrary length and i want to combine them all into a single string by a delimiter. is there a way to do that?
nevermind, i figured it out! somehow i missed that part of the lil docs.
To use K terminology, fuse is a dyad which takes a delimiter as its left argument:
"\n" fuse ("hello","world","etc") "hello\nworld\netc" ":ANYTHING:" fuse ("hello","world","etc") "hello:ANYTHING:world:ANYTHING:etc"
And you can handle even fancier cases with a recursive "format":
(list "ITEM<%s>") format ("hello","world","etc") ("ITEM<hello>","ITEM<world>","ITEM<etc>") ("\n","ITEM<%s>") format ("hello","world","etc") "ITEM<hello>\nITEM<world>\nITEM<etc>"
I may be missing something obvious, but how do you actually make a hyperlink in a field? The guided tour doesn't seem to have a script loaded into the field and the documentation doesn't specifically mention inline hyperlinks unless I missed it.
To make a hyperlink in a field, ensure that it is a "Rich Text" field (the default for new fields), switch to the Interact tool, and select a region of text within the field. You can then use the "Text -> Link..." menu item to create a hyperlink, which will be shown with a dotted underline. When you're finished editing, to make the hyperlinks "clickable" you must lock the field, by switching to the Widget tool, selecting the field, and choosing the "Widgets -> Locked" menu item.
Fields emit a "link" event when a hyperlink is clicked, which you can intercept with a script to do as you please. If you don't write a script, the default behavior is to go[] to the text you provided when you created the hyperlink. If that's the name of a card, it will navigate to that card. If it's a url like "http://google.com" it will ask the web browser to open a new tab.
Hope that helps clear things up!
Ah, gotcha! Thank you so much!
Why does distinct
not always return a list?
In the example for distinct elements given:
extract first value by value from "ABBAAC"
when all items are equal, this provides a single element instead of a single element list. I don’t know why, but in my head it seems more flexible to return a single element list.
This is what i ended up using instead, since select always returns a list.
There is another workaround here since selecting the first element of each group in ()
will give (0)
, the default value, but at least that makes sense.
on uniq x do
if x~() () else
t:select list first value by value from x
t.c0
end
end
I think the simplest way to get the edge cases you want would be using "()," to coerce lists or scalars to lists, and using "() unless" to coerce an empty result with "first" to an empty list.
(),extract () unless first value by value from "ABBBCD" ("A","B","C","D") (),extract () unless first value by value from "AAAA" ("A") (),extract () unless first value by value from "" ()
The former coercion is always valid, but the latter does require some care depending on the data; this is the nasty side of unifying nullity and a numeric value:
(),extract () unless first value by value from 1,1,0,5,1,2,0,1 (1,5,2)
Edit: and another approach entirely would be to use "dict":
range (11,22,33,0,11,22) dict () (11,22,33,0) range (11,11) dict () (11) range (0) dict () (0) range () dict () ()
Depending upon the context, you might not even need the "range".
seems like dict is the most foolproof method. unless
has the problem of ignoring zeroes.
(),extract () unless first value by value from 0,0,0
()
Among other things, Decker 1.32 revises the behavior of extract to remove its problematic "automatic de-listing".
extract first value by value from ()
Now returns (), like it ought to have from the beginning.
Is there a better way to get the characters of a string other than this?
each x in "str" x end
I would probably use "split" with an empty string as the left argument:
"" split "str" ("s","t","r")
How to display an image after a delay such as sys.ms? i understand display.text but dont know how to approach image displaying, thanks for the help in advance guys!
If you have a canvas containing the image, you can change visibility using the show
attribute:
canvas_name.show: "none"
canvas_name.show: "solid"
To show or hide it on a delay, there is a simple way and a more complex way.
The simple way uses the sleep
function. It (mostly) pauses the whole program until it’s finished sleeping. For example, you could use it in a button’s click action:
# Simple, e.g. button script
on click do
card.widgets.canvas_name.show: "none"
sleep[60] # number of frames to sleep for
card.widgets.canvas_name.show: "solid"
end
Complex uses sys.ms
and recursive go[]
functions to allow other things to occur in the meanwhile. You’d need a hidden field to store some extra data. Here the image shows after 1s.
# Complex, with a field called hidden_time
# Button script
on click do
card.widgets.canvas_name.show: "none"
card.widgets.hidden_time.text: sys.ms
go[card]
end
# Card script
on view do
elapsed: sys.ms - card.widgets.hidden_time.text
if elapsed > 1000
card.widgets.canvas_name.show: "solid"
else
go[card]
end
end
thank you so much sunil! this was very informative, i tried the sleep method.. but i goofed it lol, this explained it very well, thank you
Edit: solved!
Is there a way to include multiple where
conditions?
Example:
data: select num:("1","2","3","4","5") parity:("odd","even","odd","even","odd") prime:(0,1,1,0,1) from 0
What I want:
extract num where parity="even" and prime from data
Edit: Of course I find it as soon as I post. The answer is brackets!
extract num where (parity="even") & prime from data
how does one find the upper and lower bounds of numbers in Lil?
are there any plans to add the ability to plot a pixel in code? maybe it exists, and i haven't found it. but i think it'd be cool.
Plotting (or reading out) individual pixels on a Canvas (or an Image interface) is possible; just index or assign through them with an (x,y) pair as if they were a list or dictionary.
Plotting a large number of pixels will be fairly slow, since doing so will force the Lil interpreter to do a large number of serial operations. Both canvases and images provide a variety of higher-level methods for scaling, transforming, and drawing which operate upon pixels in bulk, and should generally be preferred, especially if the goal is any sort of realtime animation.
thanks for the advice! really loving your work here.
I couldn't find it in the manual, is there a way to hide the menubar?
PS I discovered this a few days ago, so awesome! Loved hypercard, and I'll join the decker jam :)
You can hide Decker's menu bar by "locking" a deck. In the main menu, choose "File -> Properties..". and then click "Protect..." to save a locked copy of the current deck.
In the deck file itself, this adds a line like:
locked:1
You can also manipulate whether a deck is locked on the fly by setting "deck.locked" in a script:
on click do deck.locked:!deck.locked end
(Careful, though; if you use scripts to lock a deck you haven't saved yet you might get yourself stuck!)
Edit: oh, and if you meant hide the menu bar while editing, pressing "m" on your keyboard while using drawing tools will temporarily toggle the visibility of the main menu, allowing you to draw "underneath" it.
Thanks a lot! Ow yes that second one is convenient too :)
Lilt’s write[x y]
says it will write a value to a file, can it deal with image and audio interfaces? Can lil inside decker export data to a well known image/audio format?
The "write[]" functions in both Decker and Lilt can save image interfaces as .GIF images (including transparency and animation, if desired), and it can save sound interfaces as 8khz monophonic .WAV files.
The GIF files emitted by Lilt/Decker tend to be quite large, as they make no effort to compress their image data, so it may be desirable to use ImageMagick, Gifsicle, or a similar GIF optimizer to process their output.
It's also possible to write out arbitrary binary files by using an Array interface, but this is more involved.