SLua Alpha: Difference between revisions

From Second Life Wiki
Jump to navigation Jump to search
m Signal Linden moved page Luau Alpha to SLua Alpha
Atlas Linden (talk | contribs)
No edit summary
 
(11 intermediate revisions by 5 users not shown)
Line 1: Line 1:
{{Warning|This functionality is in alpha. Instability is to be expected, and there may be very sharp edges. At this point it is expected that Luau can crash regions and perform other types of undesirable behavior.}}
{{Warning|This functionality is in alpha. Instability is to be expected, and there may be very sharp edges. At this point it is expected that Luau can crash regions and perform other types of undesirable behavior.


= Second Life Luau Alpha =
'''🚨 PLEASE NOTE Memory and performance characteristics, and API specifics may change! Scripts are currently being run in unoptimized form for development purposes.'''}}
 
= Second Life Lua (SLua) Alpha =


[[File:Luau.png|720px|thumb|right|Luau logo]]
[[File:Luau.png|720px|thumb|right|Luau logo]]


We're thrilled to announce the launch of the Luau Alpha for Second Life! This significant update introduces the Luau scripting language, offering creators enhanced performance, improved memory efficiency, and a more versatile scripting environment.
We're thrilled to announce the launch of the SLua Alpha for Second Life! This significant update introduces the Lua scripting language, offering creators enhanced performance, improved memory efficiency, and a more versatile scripting environment.


== What is Luau? ==
To get started, [https://wiki.secondlife.com/wiki/Try_SLua read the instructions here.]


Luau is a fast, small, safe, and gradually typed embeddable scripting language derived from Lua. It is designed to be backwards compatible with Lua 5.1, incorporating features from future Lua releases and expanding the feature set with type annotations and a state-of-the-art type inference system. Luau is largely implemented from scratch, with the language runtime being a heavily modified version of the Lua 5.1 runtime, featuring a completely rewritten interpreter and other performance innovations.
== What is SLua? ==


== Why Luau? ==
SLua is scripting for Second Life based on Luau, a fast, small, safe, and gradually typed embeddable scripting language derived from Lua. It is designed to be backwards compatible with Lua 5.1, incorporating features from future Lua releases and expanding the feature set with type annotations and a state-of-the-art type inference system. Luau is largely implemented from scratch, with the language runtime being a heavily modified version of the Lua 5.1 runtime, featuring a completely rewritten interpreter and other performance innovations.


The decision to integrate Luau into Second Life was driven by its ability to meet all the requirements for a scripting engine within the platform. Luau offers a high-quality scripting experience to creators, addressing many of the limitations present in the current LSL (Linden Scripting Language) environment. Its lightweight nature and performance optimizations make it an ideal choice for enhancing the scripting capabilities in Second Life. For more information on why Luau was chosen, please see the [https://wiki.secondlife.com/wiki/Lua_FAQ Lua FAQ].
== Why Lua? ==


== How to Get Started with Luau in Second Life ==
The decision to integrate Lua into Second Life was driven by its ability to meet all the requirements for a scripting engine within the platform. Lua offers a high-quality scripting experience to creators, addressing many of the limitations present in the current LSL (Linden Scripting Language) environment. Its lightweight nature and performance optimizations make it an ideal choice for enhancing the scripting capabilities in Second Life. For more information on why Lua was chosen, please see the [https://wiki.secondlife.com/wiki/Lua_FAQ Lua FAQ].


In order to play with Luau, you'll need to download our Lua project viewer, and log onto our [https://lindenlab.freshdesk.com/support/solutions/articles/31000156725-accessing-aditi Aditi beta grid].
== How to Get Started with SLua ==


* Access the latest build of the Luau-enabled Second Life Viewer from [https://releasenotes.secondlife.com/viewer/7.1.12.13526902562.html here].
In order to play with SLua, you'll need to download our Lua project viewer, and log onto our [https://lindenlab.freshdesk.com/support/solutions/articles/31000156725-accessing-aditi Aditi beta grid].


Once you've got the new viewer and have logged onto the beta grid, head over to these Luau-enabled regions:
* Access the latest build of the SLua-enabled Second Life Viewer from [https://releasenotes.secondlife.com/viewer.html here].


* [secondlife://Aditi/secondlife/Luau%20Yardang/241/235/27 Luau Yardang]
Once you've got the new viewer and have logged onto the beta grid, head over to these SLua-enabled regions:
* [secondlife://Aditi/secondlife/Luau%20Tombolo/241/235/27 Luau Tombolo]
 
* [secondlife://Aditi/secondlife/Luau%20Mesa/241/235/27 Luau Mesa]
* [secondlife://Aditi/secondlife/SLua%20Yardang/241/235/27 SLua Yardang]
* [secondlife://Aditi/secondlife/Luau%20Tideland/241/235/27 Luau Tideland]
* [secondlife://Aditi/secondlife/SLua%20Tombolo/241/235/27 SLua Tombolo]
* [secondlife://Aditi/secondlife/SLua%20Mesa/241/235/27 SLua Mesa]
* [secondlife://Aditi/secondlife/SLua%20Tideland/241/235/27 SLua Tideland]


When editing a script in the new Lua project viewer, you'll notice a new '''Compiler''' drop-down near the save button. This drop-down will allow you to select which compiler will be used, as well as which script runtime will be used (LSO2, Mono, Luau).
When editing a script in the new Lua project viewer, you'll notice a new '''Compiler''' drop-down near the save button. This drop-down will allow you to select which compiler will be used, as well as which script runtime will be used (LSO2, Mono, Luau).
Line 36: Line 40:
* '''LSL: Legacy (LSO2)''' - Scripts written in LSL, to be run on the old LSO2 VM
* '''LSL: Legacy (LSO2)''' - Scripts written in LSL, to be run on the old LSO2 VM
* '''LSL: Mono'''- Scripts written in LSL, to be run on the Mono VM
* '''LSL: Mono'''- Scripts written in LSL, to be run on the Mono VM
* '''Lua''' - Scripts written in Lua, to be run on the Luau VM
* '''Lua''' - Scripts written in Lua, to be run on the SLua VM
* '''LSL/Luau'''- Scripts written in LSL, to be run on the Luau VM
* '''LSL/Luau'''- Scripts written in LSL, to be run on the SLua VM


=== Transitioning from LSL to Luau ===
=== Transitioning from LSL to SLua ===
* '''Function Namespacing:'''
* '''Function Namespacing:'''
** In Luau, Linden Lab functions have been moved under the '''ll''' namespace.
** In SLua, Linden Lab functions have been moved under the '''ll''' namespace.
** For example:
** For example:
*** ''llSay'' becomes ''ll.Say''
*** ''llSay'' becomes ''ll.Say''
*** ''llGetPos'' becomes ''ll.GetPos''
*** ''llGetPos'' becomes ''ll.GetPos''
* '''Lists'''
* '''Lists'''
** Luau indexes begin from 1, unlike LSL where indexes begin from 0.
** Lua indexes begin from 1, unlike LSL where indexes begin from 0.
** Luau uses <code>{}</code> for ''tables'', unlike LSL where <code>[]</code> is used for ''lists''.
** Lua uses <code>{}</code> for ''tables'', unlike LSL where <code>[]</code> is used for ''lists''.
** When calling LL functions in Luau, lists often have type-strict requirements, unlike Luau in general.
* Types
*** For example, <code>ll.SetPrimitiveParams({PRIM_GLOW, 0, 1})</code> will cause a type-error because <code>0</code> is a Luau <code>number</code> type, instead of the LSL <code>integer</code> type expected by [[llSetPrimitiveParams]]. For cases like this, there are special functions which provide the correct data types.
** SLua doesn't support the usual vector/rotation syntax <code><x, y, z></code>
*** Correct: <code>ll.SetPrimitiveParams({PRIM_GLOW, integer(0), 1})</code>
*** Instead, these values are created with the functions <code>vector(x, y, z)</code>, <code>rotation(x, y, z, s)</code>
*** Similar functions exist for: '''integer''', '''uuid''' (key), '''vector''', '''quaternion''' (rotation)
*** Similar functions exist for '''uuid''' (key) and '''integer''' (distinct from the built-in '''number''' type)


=== Luau Libraries ===
=== SLua Libraries ===
* '''Coroutines:'''
* '''Coroutines:'''
** Luau supports coroutines, allowing for cooperative multitasking within scripts.
** SLua supports coroutines, allowing for cooperative multitasking within scripts.
** Key functions include:
** Key functions include:
*** ''coroutine.create''
*** ''coroutine.create''
Line 62: Line 66:
** Refer to the [https://luau.org/library#coroutine-library coroutine library documentation] for more details.
** Refer to the [https://luau.org/library#coroutine-library coroutine library documentation] for more details.
* '''Bitwise Operations:'''
* '''Bitwise Operations:'''
** Luau includes a ''bit32'' library for bitwise operations, enabling more efficient data manipulation.
** SLua includes a ''bit32'' library for bitwise operations, enabling more efficient data manipulation.
** Refer to the [https://luau.org/library#bit32-library bit32 library documentation] for more details.
** Refer to the [https://luau.org/library#bit32-library bit32 library documentation] for more details.
* '''JSON to Table Translation:'''
** SLua includes a modified [https://github.com/openresty/lua-cjson lua-cjson library] that translates tables into JSON objects and arrays and back.
** Vectors and quaternions are converted to strings, which may be changed back using the ''tovector'' and ''toquaternion'' functions, respectfully.
** Buffers are encoded as Base64 strings. They may be decoded using the ''llbase64.decode'' function.
** Functions:
*** <code>json_text = lljson.encode(some_table)</code>
*** <code>some_table = lljson.decode(json_text)</code>
* '''Base64 Encoding:'''
** A Base64 library capable of handling SLua strings and binary buffers, unlike classic ''ll'' namespace functions.
** Leveraged by the JSON functionality, but can also be called separately.
** Functions:
*** <code>base64string = llbase64.encode(string_or_buffer)</code>
*** <code>str = llbase64.decode(base64string)</code> for strings
*** <code>buf = llbase64.decode(base64string, true)</code> for buffers
* '''Standard Library:'''
* '''Standard Library:'''
** Luau comes equipped with a standard library of functions designed to manipulate built-in data types.
** SLua comes equipped with a standard library of functions designed to manipulate built-in data types.
** Explore the [https://luau.org/library Luau Standard Library] for a comprehensive list of available functions.
** Explore the [https://luau.org/library Luau Standard Library] for a comprehensive list of available functions.


== Feedback and Support ==
== Feedback and Support ==


We encourage all creators to explore the new Luau scripting capabilities and provide feedback. Your insights are invaluable in refining and enhancing this feature. For more information and to share your experiences, please refer to our [https://wiki.secondlife.com/wiki/Lua_FAQ Lua FAQ].
We encourage all creators to explore the new scripting capabilities and provide feedback. Your insights are invaluable in refining and enhancing this feature. For more information and to share your experiences, please refer to our [https://wiki.secondlife.com/wiki/Lua_FAQ Lua FAQ].


== Example Scripts ==
== Example Scripts ==


To help you get started, we've assembled some example scripts that demonstrate the capabilities of Luau in Second Life. These scripts cover various functionalities and can serve as a foundation for your own creations. Please feel free to propose changes to these scripts, or modify them to your heart's desire!
To help you get started, we've assembled some example scripts that demonstrate the capabilities of SLua. These scripts cover various functionalities and can serve as a foundation for your own creations. Please feel free to propose changes to these scripts, or modify them to your heart's desire!


=== default_script.lua ===
=== default_script.lua ===
Line 80: Line 98:
<source lang="lua">
<source lang="lua">
function state_entry()
function state_entry()
    ll.Say(0, "Hello, Avatar!")
  ll.Say(0, "Hello, Avatar!")
end
end


-- Called when the object is touched.
function touch_start(total_number)
function touch_start(total_number)
   ll.Say(0, "Touched.")
   ll.Say(0, "Touched.")
end
end


-- Invoke state_entry on startup, since simulator doesn't invoke
-- Simulate the state_entry event
-- it like it does in LSL
state_entry()
state_entry()
</source>
</source>
Line 155: Line 171:


=== user_input_coroutine.lua ===
=== user_input_coroutine.lua ===
This script demonstrates [https://www.lua.org/pil/9.html coroutines] and how they can simplify the overarching logic of a script, enabling us to write multi-event code within a single function instead of fragmenting it across separate event handlers.
This script demonstrates [https://www.lua.org/pil/9.html coroutines] and how they can simplify the overarching logic of a script, enabling us to write the bulk of our multi-event code within a centralized function instead of fragmenting across separate event handlers.
<source lang="lua">
<source lang="lua">
-- Wait for user-input before doing something useful with it.
-- Wait for user input mid-function before doing something useful with it.
main = function()
main = function(toucher)
     ll.Listen(0, "", "", "")
     local handle = ll.Listen(0, "", toucher, "")
     ll.OwnerSay("Do you want pants or gloves?")
    local event = touch_start  -- save function for later
    touch_start = nil          -- disable touch_start
 
     ll.RegionSayTo(toucher, 0, "Do you want pants or gloves?")
     local clothing = coroutine.yield() -- pause the routine's execution here
     local clothing = coroutine.yield() -- pause the routine's execution here
     ll.OwnerSay("For men or women?")
     ll.RegionSayTo(toucher, 0, "For men or women?")
     local gender = coroutine.yield()
     local gender = coroutine.yield()
     ll.OwnerSay("Favorite color?")
     ll.RegionSayTo(toucher, 0, "Favorite color?")
     local color = coroutine.yield()
     local color = coroutine.yield()
     ll.OwnerSay("Here's "..color.." "..clothing.." for "..gender)
     ll.RegionSayTo(toucher, 0, "Here's "..color.." "..clothing.." for "..gender)
     finished = true
 
    ll.ListenRemove(handle)
     touch_start = event -- restore touch_start
end
end


function touch_start(total_num)
function touch_start(total_num)
     if finished ~= false then -- unset or true
     local toucher = ll.DetectedKey(0)
        finished = false
    routine = coroutine.create(main)   -- new coroutine
        routine = coroutine.create(main) -- new coroutine
    coroutine.resume(routine, toucher) -- run coroutine (with one argument)
        coroutine.resume(routine)       -- run coroutine
    end
end
end


-- When the coroutine is suspended,
-- When the coroutine is suspended, incoming events can be handled
-- we can resume its execution by calling the coroutine again
-- and we can resume() execution of the routine
-- and pass any number of arguments to be returned by yield()
-- and pass any number of arguments to be returned by yield()
function listen(channel, name, id, message)
function listen(channel, name, id, message)
     if finished == false then
     coroutine.resume(routine, message)
        coroutine.resume(routine, message)
    end
end
end
</source>
</source>


=== dialog_coroutine.lua ===
=== multi_user_input_coroutine.lua ===
This script demonstrates how one could use coroutines to handle dialog responses, with multi-user support.
Following from the above example, how can we handle multiple users? This is where coroutines shine.
 
Instead of disabling touches to prevent others from interacting with the object, we can create new copies of the coroutine each time an avatar touches the object. We can then resume whichever coroutine is needed, based on the avatar, while all of them track their own progress separately and automagically.
<source lang="lua">
<source lang="lua">
-------------------------
-- Key: avatar uuid; Value: coroutine thread
-- Minimal EventLoop
routines = {}
-------------------------
local EventLoop = {
    -- Coroutine -> eventName it’s waiting for
    _coros = {},
    running = false
}


function EventLoop:create_task(func)
main = function(toucher)
     local coro = coroutine.create(func)
     local handle = ll.Listen(0, "", toucher, "")
    self._coros[coro] = false
    self:_run_coro(coro)
    return coro
end


function EventLoop:kill_task(coro)
    ll.RegionSayTo(toucher, 0, "Do you want pants or gloves?")
     self._coros[coro] = nil
     local clothing = coroutine.yield()
    if coroutine.status(coro) ~= "dead" then
    ll.RegionSayTo(toucher, 0, "For men or women?")
        coroutine.close(coro)
     local gender = coroutine.yield()
     end
     ll.RegionSayTo(toucher, 0, "Favorite color?")
end
     local color = coroutine.yield()
 
     ll.RegionSayTo(toucher, 0, "Here's "..color.." "..clothing.." for "..gender)
-- Internal helper: resumes a coroutine
function EventLoop:_run_coro(coro, ...)
     if coroutine.status(coro) == "dead" then
        return
    end
 
     local old_running = self.running
     self.running = true
 
    self._coros[coro] = false
    local ok, eventAwaited = coroutine.resume(coro, ...)
    self.running = old_running
 
    if not ok then
        ll.OwnerSay(`Coroutine error: {eventAwaited}`)
        self._coros[coro] = nil
        return
    end


     -- If still alive, 'eventAwaited' is the next event it wants
     ll.ListenRemove(handle)
     self._coros[coro] = eventAwaited
     routines[toucher] = nil -- Remove from collection
end
end


function EventLoop:handle_event(eventName, ...)
function touch_start(total_num)
     ll.OwnerSay(`Handling event {eventName}`)
     local toucher = ll.DetectedKey(0)
     local snapshot = table.clone(self._coros)
     local routine = routines[toucher]
     for coro, waitingFor in pairs(snapshot) do
     if not routine then -- New user needs new routine
         if coroutine.status(coro) == "dead" then
         routine = coroutine.create(main)
            self._coros[coro] = nil
        routines[toucher] = routine -- Add to collection
         elseif waitingFor == eventName then
         coroutine.resume(routine, toucher)
            ll.OwnerSay(`Dispatching event {eventName} to {coro}`)
            self:_run_coro(coro, ...)
        end
     end
     end
end
end


-- Coroutines use this to yield until an event
function listen(channel, name, id, message)
local function await_event(name)
     coroutine.resume(routines[id], message) -- Resume a specific coroutine
    assert(EventLoop.running, "await_event called outside a coroutine!")
    return coroutine.yield(name)
end
 
-------------------------
-- Script Logic
-------------------------
local buttons = {"-", "Red", "Green", "Yellow"}
local dialogInfo = "\nPlease make a choice."
 
-- Use the chat listener to feed the event-loop
function listen(channel, name, sender_id, message)
     -- We handle all 'listen' events via the event-loop
    EventLoop:handle_event(`listen_{channel}`, channel, name, sender_id, message)
end
 
-- Called when the script starts
function state_entry()
    -- Seed math.random so each new script run doesn’t repeat the same channels
    math.randomseed(ll.GetUnixTime())
    ll.OwnerSay("Script started with random channels for each user.")
end
end
-- A coroutine function for a single user's dialog flow
local function handle_dialog_for_user(userId)
    -- Use a random channel
    local channel = math.random(0x1, 0xFFFF)
    -- Create a listener for that channel
    local listenHandle = ll.Listen(channel, "", "", "")
    while true do
        -- Show the user a dialog
        ll.Dialog(userId, dialogInfo, buttons, channel)
        -- Wait for the next 'listen' event (channel, name, sender_id, message)
        local c, n, sid, msg = await_event(`listen_{channel}`)
        -- If this "listen" event isn't for our channel/user, ignore it
        if c == channel and sid == userId then
            -- If user pressed "-", re-display the menu, else they picked a final color
            if msg ~= "-" then
                ll.Say(0, `{n} selected {msg}`)
                -- Now that they've chosen something else, remove the listener and finish
                ll.ListenRemove(listenHandle)
                return
            end
        end
    end
end
-- Called when the object is touched
function touch_start(num_detected)
    for i=0, num_detected-1 do
        local toucherId = ll.DetectedKey(i)
        -- Create a separate coroutine for each person who touches
        EventLoop:create_task(function()
            handle_dialog_for_user(toucherId)
        end)
    end
end
-- Run state_entry on load:
state_entry()
</source>
</source>



Latest revision as of 14:33, 13 May 2025

Warning!

This functionality is in alpha. Instability is to be expected, and there may be very sharp edges. At this point it is expected that Luau can crash regions and perform other types of undesirable behavior.

🚨 PLEASE NOTE Memory and performance characteristics, and API specifics may change! Scripts are currently being run in unoptimized form for development purposes.


Second Life Lua (SLua) Alpha

Luau logo

We're thrilled to announce the launch of the SLua Alpha for Second Life! This significant update introduces the Lua scripting language, offering creators enhanced performance, improved memory efficiency, and a more versatile scripting environment.

To get started, read the instructions here.

What is SLua?

SLua is scripting for Second Life based on Luau, a fast, small, safe, and gradually typed embeddable scripting language derived from Lua. It is designed to be backwards compatible with Lua 5.1, incorporating features from future Lua releases and expanding the feature set with type annotations and a state-of-the-art type inference system. Luau is largely implemented from scratch, with the language runtime being a heavily modified version of the Lua 5.1 runtime, featuring a completely rewritten interpreter and other performance innovations.

Why Lua?

The decision to integrate Lua into Second Life was driven by its ability to meet all the requirements for a scripting engine within the platform. Lua offers a high-quality scripting experience to creators, addressing many of the limitations present in the current LSL (Linden Scripting Language) environment. Its lightweight nature and performance optimizations make it an ideal choice for enhancing the scripting capabilities in Second Life. For more information on why Lua was chosen, please see the Lua FAQ.

How to Get Started with SLua

In order to play with SLua, you'll need to download our Lua project viewer, and log onto our Aditi beta grid.

  • Access the latest build of the SLua-enabled Second Life Viewer from here.

Once you've got the new viewer and have logged onto the beta grid, head over to these SLua-enabled regions:

When editing a script in the new Lua project viewer, you'll notice a new Compiler drop-down near the save button. This drop-down will allow you to select which compiler will be used, as well as which script runtime will be used (LSO2, Mono, Luau).

Compiler selection dropdown

Compiler drop-down options:

  • LSL: Legacy (LSO2) - Scripts written in LSL, to be run on the old LSO2 VM
  • LSL: Mono- Scripts written in LSL, to be run on the Mono VM
  • Lua - Scripts written in Lua, to be run on the SLua VM
  • LSL/Luau- Scripts written in LSL, to be run on the SLua VM

Transitioning from LSL to SLua

  • Function Namespacing:
    • In SLua, Linden Lab functions have been moved under the ll namespace.
    • For example:
      • llSay becomes ll.Say
      • llGetPos becomes ll.GetPos
  • Lists
    • Lua indexes begin from 1, unlike LSL where indexes begin from 0.
    • Lua uses {} for tables, unlike LSL where [] is used for lists.
  • Types
    • SLua doesn't support the usual vector/rotation syntax <x, y, z>
      • Instead, these values are created with the functions vector(x, y, z), rotation(x, y, z, s)
      • Similar functions exist for uuid (key) and integer (distinct from the built-in number type)

SLua Libraries

  • Coroutines:
    • SLua supports coroutines, allowing for cooperative multitasking within scripts.
    • Key functions include:
      • coroutine.create
      • coroutine.status
      • coroutine.resume
    • Refer to the coroutine library documentation for more details.
  • Bitwise Operations:
    • SLua includes a bit32 library for bitwise operations, enabling more efficient data manipulation.
    • Refer to the bit32 library documentation for more details.
  • JSON to Table Translation:
    • SLua includes a modified lua-cjson library that translates tables into JSON objects and arrays and back.
    • Vectors and quaternions are converted to strings, which may be changed back using the tovector and toquaternion functions, respectfully.
    • Buffers are encoded as Base64 strings. They may be decoded using the llbase64.decode function.
    • Functions:
      • json_text = lljson.encode(some_table)
      • some_table = lljson.decode(json_text)
  • Base64 Encoding:
    • A Base64 library capable of handling SLua strings and binary buffers, unlike classic ll namespace functions.
    • Leveraged by the JSON functionality, but can also be called separately.
    • Functions:
      • base64string = llbase64.encode(string_or_buffer)
      • str = llbase64.decode(base64string) for strings
      • buf = llbase64.decode(base64string, true) for buffers
  • Standard Library:
    • SLua comes equipped with a standard library of functions designed to manipulate built-in data types.
    • Explore the Luau Standard Library for a comprehensive list of available functions.

Feedback and Support

We encourage all creators to explore the new scripting capabilities and provide feedback. Your insights are invaluable in refining and enhancing this feature. For more information and to share your experiences, please refer to our Lua FAQ.

Example Scripts

To help you get started, we've assembled some example scripts that demonstrate the capabilities of SLua. These scripts cover various functionalities and can serve as a foundation for your own creations. Please feel free to propose changes to these scripts, or modify them to your heart's desire!

default_script.lua

This script is roughly equivalent to the default "new script" that gets created for LSL.

function state_entry()
   ll.Say(0, "Hello, Avatar!")
end

function touch_start(total_number)
   ll.Say(0, "Touched.")
end

-- Simulate the state_entry event
state_entry()

dialog.lua

This script demonstrates how one can interact with dialog menus.

-- Define the menu buttons and dialog message.
local buttons = {"-", "Red", "Green", "Yellow"}
local dialogInfo = "\nPlease make a choice."

local ToucherID = nil
local dialogChannel = nil
local listenHandle = nil

-- This function is called when the script first starts.
function state_entry()
    -- Get the object's key and compute a dialog channel number.
    local key = ll.GetKey()
    -- Extract the last 7 characters of the key and convert it from hex.
    dialogChannel = -1 - tonumber(string.sub(tostring(key), -7, -1), 16)
end

-- Called when the object is touched.
function touch_start(num_detected)
    ToucherID = ll.DetectedKey(0)
    -- If there is already a listen handle, then remove it
    if listenHandle then
        ll.ListenRemove(listenHandle)
    end
    listenHandle = ll.Listen(dialogChannel, "", ToucherID, "")
    ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)
    -- Set a 60-second timer for response.
    ll.SetTimerEvent(60.0)
end

-- Called when a dialog response is received.
function listen(channel, name, sender_id, message)
    if message == "-" then
        -- Redisplay the dialog if the "-" option is selected.
        ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)
        return
    end
    -- Stop the timer, and stop the listening handler.
    ll.ListenRemove(listenHandle)
    ll.SetTimerEvent(0)
    -- Let the user know what they selected
    ll.Say(0, `You selected {message}`)
end

-- Called when the timer expires.
function timer()
    -- Stop the timer and clean up the listener.
    if listenHandle then
        ll.SetTimerEvent(0)
        ll.ListenRemove(listenHandle)
        ll.Whisper(0, "Sorry. You snooze; you lose.")
    end
end

-- Invoke state_entry on startup, since simulator doesn't invoke 
-- it like it does in LSL
state_entry()

user_input_coroutine.lua

This script demonstrates coroutines and how they can simplify the overarching logic of a script, enabling us to write the bulk of our multi-event code within a centralized function instead of fragmenting across separate event handlers.

-- Wait for user input mid-function before doing something useful with it.
main = function(toucher)
    local handle = ll.Listen(0, "", toucher, "")
    local event = touch_start   -- save function for later
    touch_start = nil           -- disable touch_start

    ll.RegionSayTo(toucher, 0, "Do you want pants or gloves?")
    local clothing = coroutine.yield() -- pause the routine's execution here
    ll.RegionSayTo(toucher, 0, "For men or women?")
    local gender = coroutine.yield()
    ll.RegionSayTo(toucher, 0, "Favorite color?")
    local color = coroutine.yield()
    ll.RegionSayTo(toucher, 0, "Here's "..color.." "..clothing.." for "..gender)

    ll.ListenRemove(handle)
    touch_start = event -- restore touch_start
end

function touch_start(total_num)
    local toucher = ll.DetectedKey(0)
    routine = coroutine.create(main)    -- new coroutine
    coroutine.resume(routine, toucher)  -- run coroutine (with one argument)
end

-- When the coroutine is suspended, incoming events can be handled
-- and we can resume() execution of the routine
-- and pass any number of arguments to be returned by yield()
function listen(channel, name, id, message)
    coroutine.resume(routine, message)
end

multi_user_input_coroutine.lua

Following from the above example, how can we handle multiple users? This is where coroutines shine.

Instead of disabling touches to prevent others from interacting with the object, we can create new copies of the coroutine each time an avatar touches the object. We can then resume whichever coroutine is needed, based on the avatar, while all of them track their own progress separately and automagically.

-- Key: avatar uuid; Value: coroutine thread
routines = {}

main = function(toucher)
    local handle = ll.Listen(0, "", toucher, "")

    ll.RegionSayTo(toucher, 0, "Do you want pants or gloves?")
    local clothing = coroutine.yield()
    ll.RegionSayTo(toucher, 0, "For men or women?")
    local gender = coroutine.yield()
    ll.RegionSayTo(toucher, 0, "Favorite color?")
    local color = coroutine.yield()
    ll.RegionSayTo(toucher, 0, "Here's "..color.." "..clothing.." for "..gender)

    ll.ListenRemove(handle)
    routines[toucher] = nil -- Remove from collection
end

function touch_start(total_num)
    local toucher = ll.DetectedKey(0)
    local routine = routines[toucher]
    if not routine then -- New user needs new routine
        routine = coroutine.create(main)
        routines[toucher] = routine -- Add to collection
        coroutine.resume(routine, toucher)
    end
end

function listen(channel, name, id, message)
    coroutine.resume(routines[id], message) -- Resume a specific coroutine
end

More Examples