What is new in the SLua Beta version
The SLua Beta is expected to arrive in November or December! It will first appear on the Beta Grid (at least in the current SLua regions) and in a handful of regions on the Main Grid.
I’ve gathered all the info I could find about the changes coming in this Beta. Not everything is set in stone yet, so this page may be evolving as more details become clear.
I’ve also put together some example code for SLua Beta. Keep in mind these examples can’t be tested yet and are based on assumptions. First line comments in the code are shown in red so you can easily spot that they’re not real code (at least, not yet!).
These changes are only in SLua. LSL is unchanged and stays working the same.
(this page updated on Wednesday, Oct 15th)
Events, object LLEvents
We have a new object LLEvents to work with the events. The current way to write them that SLua Alpha uses will stop working and we will need to rewrite the scripts.
LLEvents is a more flexible and dynamic way to handle the events allowing us to add or remove event handling functions at any time and to have several functions reacting to the same event.
These are the methods in the object:
- handler = LLEvents:on(name, handler) : adds an event handler.
- name : the name of the event.
- handler : the function that runs when the event happens.
- returns the same function that we have passed in, so we can use it later to remove it.
- We can add several functions to the same event, they will be called in the same order in which we add them.
- If we add the same function again, it will be called twice (or as many times as we add it) when the event triggers.
- All the functions are called when the event triggers, we can’t stop the calling sequence once we have processed the event.
- If we add a function to different events, there is no way to know which event has called it (unless the events have different number or types of parameters).
- To remove the handler we will need the returned function if we have passed an anonymous function.
- newHandler = LLEvents:once(name, handler) : adds a one-time event handler.
- name : the name of the event.
- handler : the function that runs when the event happens.
- returns a new function that we can use to remove the handler.
- The function runs only once and is automatically removed from the event afterward.
- Our function passed as handler is internally wrapped in another function and we get this new one as return.
- To remove the handler we will always need the returned function.
- found = LLEvents:off(name, handler) : removes an event handler.
- name : the name of the event.
- handler : the function we want to stop handling the event.
- returns true if the function has been found (and removed), false otherwise.
- If we have added the same function twice or more with LLEvents:on(), only the last one added will be removed.
- But not with LLEvents:once() that returns a different function each time.
- If we have added the same function twice or more with LLEvents:on(), only the last one added will be removed.
- eventsTable = LLEvents:eventNames() : returns which events are active.
- returns a table with all the event names that currently have functions handling them.
- It’s useful for debugging and to remove all the events, using it with LLEvents:listeners().
- returns a table with all the event names that currently have functions handling them.
- handlersTable = LLEvents:listeners(name) : returns which handlers are attached to an event.
- name : the name of the event.
- returns a table with the functions currenty handling the event.
- It’s useful for debugging and to remove all the functions handling an event.
When an event becomes inactive (after removing all the functions handling it) the pending events are removed from the event queue.
To change an event handler:
- Add the new one and remove the current one to preserve the pending events in the queue and handle them with the newly added function.
- Remove the current one and add the new one to discard the pending events in the queue.
We have an alternative syntax (called convenient assignment syntax) to make the change easier:
|
|
We only need to add LLEvents. to our events.
An example with the syntax of all the methods:
-- example with all the methods (SLua Beta)
-- a function to use for the example
local myListenFunction(channel, name, id, msg)
-- do something
end
-- start listening as usual
ll.Listen(1, "", "", "")
-- add an event handler
LLEvents:on("listen", myListenFunction)
-- remove the event handler
LLEvents:off("listen", myListenFunction)
-- add with anonymous function
local myListenHandler = LLEvents:on("listen",
function(channel, name, id, msg)
-- do something
end
)
-- remove
LLEvents:off("listen", myListenHandler)
-- add once
local myListenHandler = LLEvents:once("listen", myListenFunction)
-- remove
LLEvents:off("listen", myListenHandler)
-- remove all the handlers of an event
for _, myListenHandler in LLEvents:listeners("listen") do
LLEvents:off("listen", myListenHandler)
end
-- remove all the events
for _, eventName in LLEvents:eventNames() do
for _, myHandler in LLEvents:listeners(eventName) do
LLEvents:off(eventName, myHandler)
end
end
An example of use, first in Alpha and 3 different options in Beta:
-- example (SLua Alpha)
function listen(channel, name, id, msg)
if channel == 1 then
-- do something with 1
else
-- do something with 2
end
end
ll.Listen(1, "", "", "")
ll.Listen(2, "", "", "")
-- example with minimal change (SLua Beta)
function LLEvents.listen(channel, name, id, msg)
if channel == 1 then
-- do something with 1
else
-- do something with 2
end
end
ll.Listen(1, "", "", "")
ll.Listen(2, "", "", "")
-- example with one event handler (SLua Beta)
local function myListenFunction(channel, name, id, msg)
if channel == 1 then
-- do something with 1
else
-- do something with 2
end
end
LLEvents:on("listen", myListenFunction)
ll.Listen(1, "", "", "")
ll.Listen(2, "", "", "")
-- example with two event handlers (SLua Beta)
local function myListenChannel1(channel, name, id, msg)
if channel == 1 then
-- do something with 1
end
end
local function myListenChannel2(channel, name, id, msg)
-- all the event handlers are called, we need to check the parameters in all the functions
if channel == 2 then
-- do something with 2
end
end
LLEvents:on("listen", myListenChannel1)
LLEvents:on("listen", myListenChannel2)
ll.Listen(1, "", "", "")
ll.Listen(2, "", "", "")
Timers, object LLTimers
We have a new object LLTimers to work with timers. The current way to set the timer with ll.SetTimerEvent() and the event timer() that SLua Alpha uses will stop working and we will need to rewrite the scripts.
LLTimers is a more flexible and dynamic way to set the timers allowing us to use several timers and to set different functions for each interval.
These are the methods in the object:
- handler = LLTimers:on(seconds, handler) : adds a timer.
- seconds : the interval.
- handler : the function that runs when the time arrives.
- returns the same function that we have passed in, so we can use it later to remove it.
- To remove the handler we will need the returned function if we have passed an anonymous function.
- newHandler = LLTimers:once(seconds, handler) : adds a one-time timer.
- seconds : the interval.
- handler : our function that runs when the time arrives.
- returns the same function that we have passed in, so we can use it later to remove it.
- The timer runs only once and is automatically removed afterward.
- To remove the handler we will need the returned function if we have passed an anonymous function.
- This is different than LLEvents:once() that returns a new function.
- found = LLTimers:off(handler) : removes a timer.
- handler : the function we want to stop the timer.
- returns true if the timer has been removed, false otherwise.
- If we have added the same function twice or more, only the last one added will be removed.
- If we have added the same function with different intervals, we can’t stop the timer with the first interval.
- We should remove both timers and add the second one again, but its interval would start at 0.
- If we have added the same function with different intervals, we can’t stop the timer with the first interval.
- If we have added the same function twice or more, only the last one added will be removed.
There is no way to get all the functions in the timers. There is no equivalent to LLEvents:listeners().
These are the minimal changes to rewrite our scripts:
|
|
An example with the syntax of all the methods:
-- example with all the methods (SLua Beta)
-- a function to use for the example
local myTimerFunction()
-- do something
end
-- add a timer with 15 seconds
LLTimers:on(15, myTimerFunction)
-- remove the timer
LLTimers:off(myTimerFunction)
-- add with anonymous function
local myTimerHandler = LLTimers:on(15,
function()
-- do something
end
)
-- remove
LLTimers:off(myTimerHandler)
-- add once
LLTimers:once(15, myTimerFunction)
-- remove
LLTimers:off(myTimerFunction)
An example of use, first in Alpha and 2 different options in Beta:
-- example (SLua Alpha)
local ticks = 60
function timer()
-- do something every 1 second
ticks -= 1
if ticks == 0 then
-- do something every 60 seconds
ticks = 60
end
end
ll.SetTimerEvent(1)
-- example with minimal change (SLua Beta)
local ticks = 60
local function timer()
-- do something every 1 second
ticks -= 1
if ticks == 0 then
-- do something every 60 seconds
ticks = 60
end
end
LLTimers:on(1, timer)
-- example with two timers (SLua Beta)
local function myTimer1()
-- do something every 1 second
end
local function myTimer60()
-- do something every 60 seconds
end
LLTimers:on(1, myTimer1)
LLTimers:on(60, myTimer60)
Multi-events, table evts
We have a new way to work with the events, like touch_start, that can receive receive several events at once. The current way to write the multi-event events that SLua Alpha uses will stop working and we will need to rewrite the scripts.
Instead of the number of events (the parameter num_detected) the event handler receives an array table with the events.
Instead of functions like ll.DetectedKey() there are functions like GetKey() to use on each event in the table.
The multi-events are these ones:
- collision
- collision_end
- collision_start
- damage
- final_damage
- touch
- touch_end
- touch_start
- sensor
The ll.Detected* functions with their names as functions in the events table:
|
|
An example to see how it works:
-- example (SLua Alpha)
function touch_start(num_detected)
for i = 0, num_detected -1 do
local toucher = ll.GetDetectedKey(i)
-- do something
end
end
The ll.Detected* functions still work. To rewrite the script with the minimal changes we need to add:
ll = llcompatat the start of the script, llcompat is explained in the next section.num_detected = #evtsat the start of each event.
-- example with minimal change (SLua Beta)
ll = llcompat
function LLEvents.touch_start(evts)
local num_detected = #evts
for i = 0, num_detected -1 do
local toucher = ll.GetDetectedKey(i)
-- do something
end
end
2 different options with the new multi-events:
-- example with the table evts and the alternative events syntax (SLua Beta)
function LLEvents.touch_start(evts)
for _, evt in evts do
local toucher = evt:GetKey()
-- do something
end
end
-- example with the table evts (SLua Beta)
local function myTouches(evts)
for _, evt in evts do
local toucher = evt:GetKey()
-- do something
end
end
LLEvents:on("touch_start", myTouches)
Constants TRUE and FALSE don’t exist
The LSL constants TRUE and FALSE that still existed in SLua Alpha are now undefined.
- In SLua Alpha FALSE is evaluated as truthy (because is 0).
- In SLua Beta TRUE is evaluated as falsy (because is nil).
Don’t use them!
Compatibility, library llcompat
Some LL functions change they behaviour. These changes are explained in the next three sections.
We have the library llcompat with the LL functions unchanged. To use them as in SLua Alpha (and LSL) we need to add, at the start of the script:
ll = llcompat
1-based LL functions
The LL functions that have some kind of 0-based index are now 1-based.
- Negative indexes don’t change, the last element is still -1.
These are the functions and the parameters that change. The “*” added to the parameter name means that it can use negative values, we can’t just add 1 to rewrite our scripts if we are using negative values:
| ll.AdjustDamage(number, new_damage) | number |
| ll.DeleteSubList(src, start, end) | start*, end* |
| ll.DeleteSubString(src, start, end) | start*, end* |
| ll.DetectedDamage(number) | number |
| ll.DetectedGrab(number) | number |
| ll.DetectedGroup(number) | number |
| ll.DetectedKey(number) | number |
| ll.DetectedLinknumber(number) | number |
| ll.DetectedName(number) | number |
| ll.DetectedOwner(number) | number |
| ll.DetectedPos(number) | number |
| ll.DetectedRezzer(number) | number |
| ll.DetectedRot(number) | number |
| ll.DetectedTouchBinormal(index) | index |
| ll.DetectedTouchFace(index) | index |
| ll.DetectedTouchNormal(index) | index |
| ll.DetectedTouchPos(index) | index |
| ll.DetectedTouchST(index) | index |
| ll.DetectedTouchUV(index) | index |
| ll.DetectedType(number) | number |
| ll.DetectedVel(number) | number |
| ll.GetInventoryName(type, number) | number |
| ll.GetListEntryType(src, index) | index* |
| ll.GetNotecardLine(name, line) | line |
| ll.GetNotecardLineSync(name, line) | line |
| ll.GetSubString(src, start, end) | start*, end* |
| ll.InsertString(dst, pos, src) | pos |
| ll.KeysKeyValue(first, count) | first |
| ll.LinksetDataFindKeys(pattern, start, count) | start |
| ll.LinksetDataListKeys(start, count) | start |
| ll.List2Float(src, index) | index* |
| ll.List2Integer(src, index) | index* |
| ll.List2Key(src, index) | index* |
| ll.List2List(src, start, end) | start*, end* |
| ll.List2ListSlice(src, start, end, stride, slice_index) | start*, end*, slice_index* |
| ll.List2ListStrided(src, start, end, stride) | start*, end* |
| ll.List2Rot(src, index) | index* |
| ll.List2String(src, index) | index* |
| ll.List2Vector(src, index) | index* |
| ll.ListFindList(src, test) | <return> |
| ll.ListFindListNext(src, test, instance) | <return>, instance* |
| ll.ListFindStrided((src, test, start, end, stride) | <return>, start*, end* |
| ll.ListInsertList(dest, src, start) | start* |
| ll.ListReplaceList(dest, src, start, end) | start*, end* |
| ll.ListSortStrided(src, stride, stride_index, ascending) | stride_index* |
| ll.Ord(val, index) | index* |
| ll.SubStringIndex(source, pattern) | <return> |
LL functions return nil when not found
The LL functions that returned -1 meaning “not found” now return nil.
These are the functions that change:
| ll.ListFindList |
| ll.ListFindListNext |
| ll.ListFindStrided |
| ll.SubStringIndex |
boolean LL functions
The LL functions that return a boolean value now return type boolean instead of type number.
- Functions like ll.GetPrimitiveParams() and ll.GetObjectDetails() that return boolean values inside lists still return them as type number.
- LL functions are not ready to return type boolean in a list.
These are the functions that change:
| ll.AgentInExperience |
| ll.DerezObject |
| ll.DetectedGroup |
| ll.EdgeOfWorld |
| ll.GetScriptState |
| ll.GetStatus |
| ll.IsFriend |
| ll.IsLinkGLTFMaterial |
| ll.ManageEstateAccess |
| ll.OverMyLand |
| ll.SameGroup |
| ll.ScaleByFactor |
| ll.ScriptDanger |
| ll.SetMemoryLimit |
| ll.SetRegionPos |
| ll.VerifyRSA |
Others
- Script memory : SLua scripts will have 128k of memory.
- LSL scripts compiled to VM Luau will also have 128k.
- LSL Mono will stay with 64k.
- Linkset Data will stay with 128k.
- Default “new script” : There will be a different script, without using state_entry() and with ll.OwnerSay() instead of ll.Say().
SLua editor
There will be an official SLua extension for the Visual Studio Code editor. It could be ready at a different moment than SLua Beta.
The SLua extension is based on open source VSC extensions:
- Luau-LSP : a Luau extension that uses the Luau/Roblox code
- It uses the Luau Analyzer for type checking and linting.
- Type checking automatically infers types from the values assigned to variables or manually by adding type annotations. These annotations can define types, combinations of types, or subtypes. There are directives that allow to control the level of type checking in each script, ranging from none to strict.
- Linting identifies possible issues like uninitialized or unused variables, duplicated functions, mismatched parameter counts, return values, and many more. There are also directives to enable or disable specific linting checks.
- It uses the Luau Analyzer for type checking and linting.
- Selene : a Lua and Luau extension.
- Linting focusing on style rules that can be configured to adapt to each scripter style.
- It helps enforce a consistent and readable coding style.
- Linting focusing on style rules that can be configured to adapt to each scripter style.
The SLua extension will have a preprocessor.
- Probably with the usual commands: #define, #undef, #ifdef, #ifndef, #else, #endif, #if, #elif
- The syntax is not known yet, probably not starting with # that is a SLua operator)
- An include/require function.
- Files are imported at compile time by the preprocessor.
- It imports files accessible from VSC.
- It’s not known if it can import inworld scripts. If it was the case, the scripts should be in the inventory and full perm, and the viewer should be opened to import them.
- It can’t import no-modify scripts. There is no way to distribute libraries without the source code.
- With the viewer opened, scripts can be executed from VSC and the error, debug channel and OwnerSay messages are shown in VSC.
- But no debugging option at all.
An example of include/require:
-- a library of functions ready to import (SLua Beta with VSC extension)
-- all variables should be local to avoid name conflicts when imported
local greetings = {}
function greetings.hi(name)
print("Hello " .. name .. "!")
end
function greetings.bye(name)
print("See you " .. name .. "!")
end
-- libraries have to return something, often a table with functions
return greetings
-- a script using include/require (SLua Beta with VSC extension)
local greet = require("greetings.slua") -- the syntax is unknown
greet.hi(ll.GetOwner())
-- some more code
greet.bye(ll.GetOwner())
-- the same script after preprocessing and ready to compile (SLua Beta with VSC extension)
-- the imported code is wrapped in an anonymous function to avoid name conflicts
-- the anonymous function is executed assigning the return to the variable
local greet = (function()
local greetings = {}
function greetings.hi(name)
print("Hello " .. name .. "!")
end
function greetings.bye(name)
print("See you " .. name .. "!")
end
return greetings
end)()
greet.hi(ll.GetOwner())
-- some more code
greet.bye(ll.GetOwner())
There will also be an official LSL extension for VSC, appearing at the same time or later than the SLua extension.
The inworld editor will be improved later (in an unknown later).