<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.secondlife.com/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Signal+Linden</id>
	<title>Second Life Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.secondlife.com/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Signal+Linden"/>
	<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/wiki/Special:Contributions/Signal_Linden"/>
	<updated>2026-07-27T05:01:14Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.42.1</generator>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Third_Party_Viewer_Directory/Aperture_Viewer&amp;diff=1218439</id>
		<title>Third Party Viewer Directory/Aperture Viewer</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Third_Party_Viewer_Directory/Aperture_Viewer&amp;diff=1218439"/>
		<updated>2025-08-29T15:46:50Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: https://github.com/secondlife/third-party-viewers/issues/8&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=={{TPVD-Icon|Aperture_Viewer}} Aperture Viewer==&lt;br /&gt;
&lt;br /&gt;
{{TPVD-Detail&lt;br /&gt;
|home=https://apertureviewer.com&lt;br /&gt;
|privacy=https://github.com/ApertureViewer/Aperture-Viewer/wiki/Privacy-Policy&lt;br /&gt;
|download=https://github.com/ApertureViewer/Aperture-Viewer/releases&lt;br /&gt;
|source=https://github.com/ApertureViewer/Aperture-Viewer&lt;br /&gt;
|supportemail=contact@apertureviewer.com&lt;br /&gt;
|slcontact=ApertureViewerOfficial&lt;br /&gt;
|rlcontact=&lt;br /&gt;
|platforms={{TPVD-Platform|Windows|showtext=1}}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Aperture Viewer is an advanced, open-source viewer delivering exceptional visual quality, powerful creative tools, and strong performance for Second Life and OpenSim. Created with artists in mind—but welcoming and accessible to everyone—Aperture Viewer offers an unmatched combination of visual control, thoughtful privacy features, and intuitive usability, enhancing the virtual experience for all users.&lt;br /&gt;
&lt;br /&gt;
{{TPVD-Footer}}&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=LlSetGroundTexture&amp;diff=1218410</id>
		<title>LlSetGroundTexture</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=LlSetGroundTexture&amp;diff=1218410"/>
		<updated>2025-08-11T15:49:35Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{LSL_Function&lt;br /&gt;
|inject-2&lt;br /&gt;
|func_id=|func_sleep=0.0|func_energy=10.0&lt;br /&gt;
|sort=SetGroundTexture&lt;br /&gt;
|func=llSetGroundTexture|sort=SetGroundTexture&lt;br /&gt;
|return_type=integer&lt;br /&gt;
|p1_type=list|p1_name=changes|p1_desc=List of changes to apply to ground textures on the region.&lt;br /&gt;
|func_desc=&lt;br /&gt;
Changes the textures used to paint the terrain of a region. The owner of the script must be able to manage the estate.&lt;br /&gt;
|return_text&lt;br /&gt;
|spec&lt;br /&gt;
|caveats=&lt;br /&gt;
* This function must be executed from a script in an object owned by the region owner or an estate manager.&lt;br /&gt;
* This function is throttled at 4 calls every 2 minutes.&lt;br /&gt;
* Always returns 0&lt;br /&gt;
* PBR terrain is all or nothing. Mixing normal terrain textures with PBR textures will cause the PBR textures to appear gray.&lt;br /&gt;
|constants=&lt;br /&gt;
{{LSL Constants/GroundTexture}}&lt;br /&gt;
|examples&lt;br /&gt;
|helpers&lt;br /&gt;
|also_functions=&lt;br /&gt;
{{LSL DefineRow||[[llModifyLand]]|Modifies terrain contours.}}&lt;br /&gt;
|also_tests&lt;br /&gt;
|also_events&lt;br /&gt;
|also_articles&lt;br /&gt;
|notes&lt;br /&gt;
|cat1=Ground&lt;br /&gt;
|cat2=Region&lt;br /&gt;
|cat3&lt;br /&gt;
|cat4&lt;br /&gt;
}}&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Third_Party_Viewer_Directory&amp;diff=1218314</id>
		<title>Third Party Viewer Directory</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Third_Party_Viewer_Directory&amp;diff=1218314"/>
		<updated>2025-05-21T15:27:34Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Add Aperture Viewer&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{KBwarning|width=90%| The viewers listed here are not developed nor distributed by Linden Lab.  Being listed here means only that the developers of the viewer have represented that they comply with the [http://secondlife.com/corporate/tpv.php Policy on Third-Party Viewers] and the [http://secondlife.com/corporate/tos.php Second Life Terms of Service]. You are responsible for evaluating whether you want to use and share information with them.}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;noinclude&amp;gt;{{OS License Nav}}&amp;lt;/noinclude&amp;gt;&lt;br /&gt;
==Using third party viewers==&lt;br /&gt;
&lt;br /&gt;
You may connect to Second Life using software released by a third-party developer. Linden Lab provides a [http://secondlife.com/corporate/tpv.php Policy on Third-Party Viewers] to promote a positive and predictable experience for all Second Life Residents. Beware of third-party viewers that are not in the Viewer Directory: they have either declined to self-certify their compliance with Linden Lab policies or been refused for noncompliance with the policies.&lt;br /&gt;
&lt;br /&gt;
==Active Full Viewers==&lt;br /&gt;
&lt;br /&gt;
These viewers are projects that are updated regularly to track new developments in the Linden Lab viewer, and implement a full graphical environment.&lt;br /&gt;
&lt;br /&gt;
{|border=&amp;quot;1&amp;quot; style=&amp;quot;width: 100%; border-collapse: separate; border-spacing: 0; border-width: 1px; border-style: solid; border-color: #000; padding: 0;&amp;quot;&lt;br /&gt;
|- style=&amp;quot;background-color: #dfd;&amp;quot;&lt;br /&gt;
!Viewer&lt;br /&gt;
!Description&lt;br /&gt;
!Platforms&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Alchemy|An experimental Second Life™ viewer striving to be at the forefront of stability, performance, and technological advancement in the open-source metaverse viewer field.|{{TPVD-Platform|Windows}}{{TPVD-Platform|MacOS}}{{TPVD-Platform|Linux}}|iconversion=2}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Aperture Viewer|A powerful, open-source viewer for Second Life and OpenSim, offering exceptional visuals, creative tools, and intuitive features designed for artists and accessible to all.|{{TPVD-Platform|Windows}}}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Black Dragon|&amp;lt;!-- {{KBcaution|custom=&amp;amp;nbsp;|Uses non-standard and misleading values for avatar complexity}} --&amp;gt;&lt;br /&gt;
The primary goal of Black Dragon is to enhance and refine the visuals of Second Life as well as having unique design approaches and features.|{{TPVD-Platform|Windows}}}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Firestorm|The next generation viewer from The Phoenix Firestorm Project, Firestorm is based on the LL V3 LGPL code and offers extensive interface customizability including V1 skin options, feature and option rich with 24/7 support.|{{TPVD-Platform|Windows}}{{TPVD-Platform|MacOS}}{{TPVD-Platform|Linux}}}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Kirstens|A hybrid viewer based on a mixture of official sources and custom code aimed specifically at experienced users, with emphasis on the visuals.|{{TPVD-Platform|Windows}}|iconversion=4}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Kokua|Kokua provides releases for PC, Mac and Linux with and without Marine Kelley&#039;s RLV together with a broad selection of original features and the best from other viewers.&lt;br /&gt;
&lt;br /&gt;
Kokua aims to be the viewer of choice for RLV users who want Marine Kelley&#039;s original implementation with more bells and whistles than Marine&#039;s own viewer.|{{TPVD-Platform|Windows}}{{TPVD-Platform|MacOS}}{{TPVD-Platform|Linux}}{{TPVD-Platform|Linux64}}}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Catznip|Catznip aims to refine and reinvent your window into Second Life. Our focus is stability, usability, privacy and plenty of new original home grown features. It&#039;s about taking a fresh look at things and attempting to do them better.|{{TPVD-Platform|Windows}}{{TPVD-Platform|Linux}}|iconversion=2}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Restrained Love|The RLV enhances your experience when you use it in conjunction with RLV-compatible objects,  which are able to restrict the user from detaching items, receiving IMs, hearing chat, teleporting etc.|{{TPVD-Platform|Windows}}{{TPVD-Platform|MacOS}}}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Singularity|Singularity strives to combine the beloved look and feel of Viewer 1.23 with the latest and greatest of available technology, and to stay compatible with future Second Life changes and features.|{{TPVD-Platform|Windows}}{{TPVD-Platform|Linux}}}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Genesis|Genesis is a blazingly fast, high performance 1.23 style viewer for Second Life, offering the very latest features, never seen before in a Legacy viewer. |{{TPVD-Platform|Windows}}}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|CoolVLViewer|An improved and expanded v1-UI viewer kept in close sync with Linden Lab&#039;s official viewer features. Geared towards performances and stability. The oldest maintained TPV (first release date: 2007-11-16)|{{TPVD-Platform|Windows}}{{TPVD-Platform|MacOS}}{{TPVD-Platform|Linux}}}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Megapahit|This fork is planned to be the shelter for the metaverse refugees, especially those from less supported operating systems|{{TPVD-Platform|MacOS}}{{TPVD-Platform|Linux}}}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Lovense|Lovense Viewer is a free third-party viewer for Second Life that enables seamless integration with Lovense products.|{{TPVD-Platform|MacOS}}{{TPVD-Platform|Windows}}}}&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Subset Viewers==&lt;br /&gt;
&lt;br /&gt;
These viewers omit some aspects of the full viewer functionality.&lt;br /&gt;
&lt;br /&gt;
{|border=&amp;quot;1&amp;quot; style=&amp;quot;width: 100%; border-collapse: separate; border-spacing: 0; border-width: 1px; border-style: solid; border-color: #000; padding: 0;&amp;quot;&lt;br /&gt;
|- style=&amp;quot;background-color: #dfd;&amp;quot;&lt;br /&gt;
!Viewer&lt;br /&gt;
!Description&lt;br /&gt;
!Platforms&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Radegast|Light-weight feature rich &#039;&#039;&#039;non-graphical&#039;&#039;&#039; client, ideal for situations where full 3D rendering client is less than ideal option, for instance, an office environment, running on low performance computers and similar.|{{TPVD-Platform|Windows}}{{TPVD-Platform|MacOS}}{{TPVD-Platform|Linux}}}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|SpeedLight|Pure web Second Life viewer. Supports desktop and all mobile browsers, allows switching between devices.|{{TPVD-Platform|Windows}}{{TPVD-Platform|MacOS}}{{TPVD-Platform|Linux}}{{TPVD-Platform|Android}}{{TPVD-Platform|iPhone}}|iconversion=1}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|MetaChat|iOS - TEXT ONLY - Second Life® client.|{{TPVD-Platform|iPhone}}|iconversion=1}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Mobile Grid Client|Second Life and Open Simulator messaging client for all Android powered devices.|{{TPVD-Platform|Android}}}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|LittleSight|Second Life viewer for Android.{{KBcaution|custom=&amp;amp;nbsp;|Severely restricts movement until user pays for additional service}}|{{TPVD-Platform|Android}}}}&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|GroupTools|GroupTools is a lightweight viewer (Text only) that includes features to easier deal with SecondLife group activities such as sending notices and IM&#039;s. While GroupTools main focus is group activities, a few other features are available as well. |{{TPVD-Platform|Windows}}}}&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
==Out Of Date Viewers==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{KBcaution|custom=&amp;amp;nbsp;|These viewers have not been updated recently enough to be considered fully compatible with current Second Life services. Depending on your usage, they may or may not meet your needs.}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{|border=&amp;quot;1&amp;quot; style=&amp;quot;width: 100%; border-collapse: separate; border-spacing: 0; border-width: 1px; border-style: solid; border-color: #000; padding: 0;&amp;quot;&lt;br /&gt;
|- style=&amp;quot;background-color: #dfd;&amp;quot;&lt;br /&gt;
!Viewer&lt;br /&gt;
!Description&lt;br /&gt;
!Platforms&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Exodus|A new flavour of the Second Life Viewer 3 series, packed with useful features to enhance the visual experience and competitive play in Second Life.|{{TPVD-Platform|Windows}}{{TPVD-Platform|MacOS}}{{TPVD-Platform|Linux}}}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Imprudence|The goal of Imprudence is to improve the user interface and usability of the viewer through community involvement, thoughtful design, modern development methods, and a pro-change atmosphere.&lt;br /&gt;
|{{TPVD-Platform|Windows}}{{TPVD-Platform|MacOS}}{{TPVD-Platform|Linux}}}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|UKanDo|Yet Another Second Life Viewer!|{{TPVD-Platform|Windows}}}}&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
{{TPVD-Listing|Pocket Metaverse|iPhone and iPad Client for Second Life®|{{TPVD-Platform|iPhone}}|iconversion=2}}&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color: #dfd; margin: 20px 10px 10px 10px; padding: 1px 10px 10px 10px;&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==For developers: how to apply to list a viewer in this directory==&lt;br /&gt;
&lt;br /&gt;
# Your Second Life accounts must be in good standing, must not be suspended, and must not have been permanently banned or terminated.&lt;br /&gt;
# You must have valid payment information on file with us or have been age verified by us.&lt;br /&gt;
# Review our Policy on Third-Party Viewers and the Second Life Terms of Service.&lt;br /&gt;
# Make sure you and your third-party viewer are in compliance with our policies.&lt;br /&gt;
# Open an [https://github.com/secondlife/third-party-viewers/issues/new/choose Application Issue] on the [https://github.com/secondlife/third-party-viewers Third Party Viewer] Github repository and submit it.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color: #fdd; margin: 20px 10px 10px 10px; padding: 1px 10px 10px 10px;&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Reporting policy violations==&lt;br /&gt;
&lt;br /&gt;
If you believe that you have direct evidence that any third party viewer (whether listed in the Third Party Viewer Directory or not) may be in violation of the [http://secondlife.com/corporate/tpv.php Second Life Terms of Service] or the [http://secondlife.com/corporate/tpv.php Policy on Third Party Viewers], you can [https://github.com/secondlife/third-party-viewers/issues/new/choose file a violation report] to be investigated. &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
[[Category:Third Party Viewers]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=File:TPV-ApertureViewer-Icon.png&amp;diff=1218313</id>
		<title>File:TPV-ApertureViewer-Icon.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=File:TPV-ApertureViewer-Icon.png&amp;diff=1218313"/>
		<updated>2025-05-21T15:24:16Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Signal Linden moved page File:TPV-ApertureViewer-Icon.png to File:TPV-Aperture Viewer-Icon.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[File:TPV-Aperture Viewer-Icon.png]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=File:TPV-Aperture_Viewer-Icon.png&amp;diff=1218312</id>
		<title>File:TPV-Aperture Viewer-Icon.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=File:TPV-Aperture_Viewer-Icon.png&amp;diff=1218312"/>
		<updated>2025-05-21T15:24:15Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Signal Linden moved page File:TPV-ApertureViewer-Icon.png to File:TPV-Aperture Viewer-Icon.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Third_Party_Viewer_Directory/Aperture_Viewer&amp;diff=1218311</id>
		<title>Third Party Viewer Directory/Aperture Viewer</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Third_Party_Viewer_Directory/Aperture_Viewer&amp;diff=1218311"/>
		<updated>2025-05-21T15:21:52Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Fix icon URL&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=={{TPVD-Icon|Aperture_Viewer}} Aperture Viewer==&lt;br /&gt;
&lt;br /&gt;
{{TPVD-Detail&lt;br /&gt;
|home=https://github.com/ApertureViewer/Aperture-Viewer/wiki&lt;br /&gt;
|privacy=https://github.com/ApertureViewer/Aperture-Viewer/wiki/Privacy-Policy&lt;br /&gt;
|download=https://github.com/ApertureViewer/Aperture-Viewer/wiki/Download-&amp;amp;-Installation&lt;br /&gt;
|source=https://github.com/ApertureViewer/Aperture-Viewer&lt;br /&gt;
|supportemail=Aperture &#039;&#039;dot&#039;&#039; Viewer &#039;&#039;at&#039;&#039; gmail &#039;&#039;dot&#039;&#039; com&lt;br /&gt;
|slcontact=William Weaver (paperwork) &lt;br /&gt;
|rlcontact=&lt;br /&gt;
|platforms={{TPVD-Platform|Windows|showtext=1}}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Aperture Viewer is an advanced, open-source viewer delivering exceptional visual quality, powerful creative tools, and strong performance for Second Life and OpenSim. Created with artists in mind—but welcoming and accessible to everyone—Aperture Viewer offers an unmatched combination of visual control, thoughtful privacy features, and intuitive usability, enhancing the virtual experience for all users.&lt;br /&gt;
&lt;br /&gt;
{{TPVD-Footer}}&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Third_Party_Viewer_Directory/Aperture_Viewer&amp;diff=1218310</id>
		<title>Third Party Viewer Directory/Aperture Viewer</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Third_Party_Viewer_Directory/Aperture_Viewer&amp;diff=1218310"/>
		<updated>2025-05-21T15:20:47Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Created page with &amp;quot;=={{TPVD-Icon|ApertureViewer}} Aperture Viewer==  {{TPVD-Detail |home=https://github.com/ApertureViewer/Aperture-Viewer/wiki |privacy=https://github.com/ApertureViewer/Apertur...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=={{TPVD-Icon|ApertureViewer}} Aperture Viewer==&lt;br /&gt;
&lt;br /&gt;
{{TPVD-Detail&lt;br /&gt;
|home=https://github.com/ApertureViewer/Aperture-Viewer/wiki&lt;br /&gt;
|privacy=https://github.com/ApertureViewer/Aperture-Viewer/wiki/Privacy-Policy&lt;br /&gt;
|download=https://github.com/ApertureViewer/Aperture-Viewer/wiki/Download-&amp;amp;-Installation&lt;br /&gt;
|source=https://github.com/ApertureViewer/Aperture-Viewer&lt;br /&gt;
|supportemail=Aperture &#039;&#039;dot&#039;&#039; Viewer &#039;&#039;at&#039;&#039; gmail &#039;&#039;dot&#039;&#039; com&lt;br /&gt;
|slcontact=William Weaver (paperwork) &lt;br /&gt;
|rlcontact=&lt;br /&gt;
|platforms={{TPVD-Platform|Windows|showtext=1}}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Aperture Viewer is an advanced, open-source viewer delivering exceptional visual quality, powerful creative tools, and strong performance for Second Life and OpenSim. Created with artists in mind—but welcoming and accessible to everyone—Aperture Viewer offers an unmatched combination of visual control, thoughtful privacy features, and intuitive usability, enhancing the virtual experience for all users.&lt;br /&gt;
&lt;br /&gt;
{{TPVD-Footer}}&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Template:TPVD-Icon&amp;diff=1218309</id>
		<title>Template:TPVD-Icon</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Template:TPVD-Icon&amp;diff=1218309"/>
		<updated>2025-05-21T15:18:37Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;includeonly&amp;gt;{{#ifexist: File:TPV-{{{1}}}{{#if: {{{iconversion|}}}|-{{{iconversion}}}|}}-Icon.png|[[File:TPV-{{{1}}}{{#if: {{{iconversion|}}}|-{{{iconversion}}}|}}-Icon.png|48px|middle|link=Third Party Viewer Directory/{{{1}}}|{{{1}}}]]|}}&amp;lt;/includeonly&amp;gt;&amp;lt;noinclude&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Upload as &#039;&#039;&#039;File:TPV-&#039;&#039;&#039;&#039;&#039;viewername&#039;&#039;{&#039;&#039;&#039;-&#039;&#039;&#039;&#039;&#039;iconversion&#039;&#039;}&#039;&#039;&#039;-Icon.png&#039;&#039;&#039; and then use macros like:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 {{TPVD-Icon|Phoenix}} Phoenix&lt;br /&gt;
&lt;br /&gt;
 {{TPVD-Icon|Catznip|iconversion=2}} Catznip&lt;br /&gt;
&lt;br /&gt;
 {{TPVD-Icon|Bogus}} Bogus&lt;br /&gt;
 &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{TPVD-Icon|Phoenix}}Phoenix&lt;br /&gt;
&lt;br /&gt;
{{TPVD-Icon|Catznip|iconversion=2}}Catznip&lt;br /&gt;
&lt;br /&gt;
{{TPVD-Icon|MetaChat|iconversion=1}} MetaChat&lt;br /&gt;
&lt;br /&gt;
{{TPVD-Icon|Bogus}}Bogus&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=File:TPV-Aperture_Viewer-Icon.png&amp;diff=1218308</id>
		<title>File:TPV-Aperture Viewer-Icon.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=File:TPV-Aperture_Viewer-Icon.png&amp;diff=1218308"/>
		<updated>2025-05-21T15:15:09Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Talk:Lua_FAQ&amp;diff=1218096</id>
		<title>Talk:Lua FAQ</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Talk:Lua_FAQ&amp;diff=1218096"/>
		<updated>2025-03-21T17:12:20Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Signal Linden moved page Talk:Lua FAQ to Talk:SLua FAQ&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I really love the LSL Portal and so would appreciate if Lua can get the same treatment or coexist without causing confusion to existing or new scripters.&lt;br /&gt;
&lt;br /&gt;
So I&#039;m wondering about technical documentation and conflicts with LSL. Perhaps a) it might be wisest to create a &amp;quot;Lua Portal&amp;quot; that mirrors the LSL Portal but then is expanded over time. And/Or b) would all LSL functions pages (and the LSL Function template) be updated to show the Lua version? c) What if there is Lua-only function later or syntactical differences esp with functions that use or return lists as lists may not exist in Lua or work differently to Lua tables? And d) would a lot of the function documentation diverge too much due to differences in the languages in how they may need to treat types, operators and example code?&lt;br /&gt;
&lt;br /&gt;
It seems a [[Lua Portal]] will be inevitable anyway, because there needs to be a central location where all the Lua-specific pages can be accessed from easily, such as developer resources (tutorials, guides and script examples and libraries), and Lua language reference just as easily as for LSL and also avoid confusing beginners dealing with two different languages on the platform.&lt;br /&gt;
&lt;br /&gt;
Another thing to look at might be that there are a lot of LSL-specific pages that may need to be prefixed with LSL_ in their pages still, e.g. [[State]], [[While]], [[If]], etc since Lua will have differences to LSL. On the other hand there are also some other portals that also invade the global namespace e.g. [[Coding Standard]], [[Features]], [[Glossary]]. All of this affects search functionality. A disambiguation page or template akin to wikipedia may be necessary in place of some of the existing redirects. If someone searches for &amp;quot;string&amp;quot; or &amp;quot;if&amp;quot; they get fed with LSL currently.&lt;br /&gt;
&lt;br /&gt;
-- [[User:Nexii Malthus|&amp;lt;span style=&amp;quot;color: #111; text-shadow:-1px -1px #ddd, 1px 1px #888;&amp;quot;&amp;gt;Nexii Malthus&amp;lt;/span&amp;gt;]] 14:19, 4 July 2024 (PDT)&lt;br /&gt;
&lt;br /&gt;
: Hear, hear. I foresee a lot of work ahead of us SLWikians. If there is a direct 1:1 correspondence between LSL and Lua, well, in that case, we could do something similar to what Microsoft does on its documentation for .NET, where you get, for the same function, the C# and the VisualBasic variants — the idea being that the function is the &#039;&#039;same&#039;&#039;, it&#039;s just the language construct that uses the function that will change.&lt;br /&gt;
: Other similar cases are those explaining, say, a RESTful API. The overall parameters are described, and then you get code snippets for implementing that call in several different programming languages, e.g. cURL/JavaScript/Python/PHP/Go, for instance.&lt;br /&gt;
: However, I can imagine that this simplistic approach will fail in our case, especially if we start getting some divergence between actual functions (i.e., new functions that only make sense in Lua, such as those dealing with JSON; and old functions that only make sense in LSL, such as those to retrieve values from a List). While (speculatively) Lua could have its own &amp;quot;List-like&amp;quot; type — it shouldn&#039;t be hard to implement — I can imagine that accessing its members will look quite different. Or perhaps not. In any case, there will be differences. It might make more sense to have a &amp;quot;Lua portal&amp;quot; instead, and just link together equivalent functionality (so that you can look up something and see both its LSL and Lua equivalents).&lt;br /&gt;
: Exciting times ahead :-) Lots of work to do as well. — [[User:Gwyneth Llewelyn|Gwyneth Llewelyn]] ([[User talk:Gwyneth Llewelyn|talk]]) 01:39, 29 August 2024 (PDT)&lt;br /&gt;
&lt;br /&gt;
:: A friend suggested moving to github with markdown files for LSL. At first I was against it, I mean I really, really like the wiki you know? Its quirks and editing, the powerful template system. All the awesome content and contributed scripts.&lt;br /&gt;
:: But in hindsight, looking at MDN Web Docs and other community efforts out there, it looks like that&#039;s the way to go these days for documentation sites. There&#039;s also the huge issue around having to gain wiki editing access which added to moving away.&lt;br /&gt;
:: At least for Lua I think it will make sense going forward. I really don&#039;t think there is a nice way to layer another language on a mediawiki that has tons of legacy content centered around one scripting language.&lt;br /&gt;
:: The Lua Portal will be at most a convenient jumping off point to the technical documentation. -- [[User:Nexii Malthus|&amp;lt;span style=&amp;quot;color: #111; text-shadow:-1px -1px #ddd, 1px 1px #888;&amp;quot;&amp;gt;Nexii Malthus&amp;lt;/span&amp;gt;]] 05:50, 1 September 2024 (PDT)&lt;br /&gt;
&lt;br /&gt;
== I 💙 Lua (well, not really, but ok)! Here are a few suggestions but also some questions: ==&lt;br /&gt;
&lt;br /&gt;
All right, so I stumbled upon this page just today, after the general announcements posted on the forums. While I was aware of the effort of doing client-side Lua scripting, I had zero knowledge of the amazing work having been done &amp;quot;under the hood&amp;quot; to get LSL running on top of Luau — that idea never even crossed my simple mind, and aye, it &#039;&#039;does&#039;&#039; sound crazy indeed.&lt;br /&gt;
&lt;br /&gt;
On the other hand, perhaps it&#039;s not so surprising. As I&#039;m fond to quote all over the place, Lua was &#039;&#039;designed&#039;&#039; to be an embedded programming language for games and other applications. The keyword here is &amp;quot;designed for embedding&amp;quot;. Obviously, you &#039;&#039;can&#039;&#039; embed JavaScript, or Python, or LIST (a popular choice in the recent past!), or naturally Java, and you can even embed C#, and there are several other languages that I&#039;m sure that someone, somewhere, has written an &amp;quot;embeddable&amp;quot; version of whatever programming language has been invented out there. Since I saw that the OpenSimulator code included an embedded Prolog interpreter written in C# just to do &#039;&#039;one&#039;&#039; function, I can believe that anything is possible!&lt;br /&gt;
&lt;br /&gt;
That does not mean that it&#039;s efficient, clean, easy, with a small footprint, etc. It just means that these embedded languages &#039;&#039;work&#039;&#039;. That&#039;s the goal, after all.&lt;br /&gt;
&lt;br /&gt;
Lua, by contrast, is not merely &amp;quot;the programming language&amp;quot;. Syntax-wise, it has a wonderful long list of awesome features and concepts that make things insanely easy to do — aye, even compared to JavaScript. But it also has some terribly annoying quirks which make zero sense and are just a stupid way of &amp;quot;differentiating&amp;quot; the language from others in the same family. It&#039;s stupid to use &amp;lt;code&amp;gt;~=&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;!=&amp;lt;/code&amp;gt;. Indeed, I find it even stranger due to Lua&#039;s Brazilian origin — the key for the tilde &amp;lt;code&amp;gt;~&amp;lt;/code&amp;gt; is a dead key (used for the Portuguese vowels ã and õ), which means that, on a Portuguese keyboard, you need to press &#039;&#039;three&#039;&#039; keys to get the negation symbol!&lt;br /&gt;
&lt;br /&gt;
Comments are also awful. I know that the &amp;quot;proper&amp;quot; way of doing comments is open to debate, but, at the end of the day, C&#039;s 50+-years-legacy of &amp;lt;code&amp;gt;/* ... */&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;//&amp;lt;/code&amp;gt; (the latter &amp;quot;borrowed&amp;quot; from C++ and &amp;quot;backported&amp;quot; to C as well) is what everybody uses, at least on languages that are sensible enough not to consider whitespace as syntactically relevant (Python, I&#039;m looking at &#039;&#039;you&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
And arrays starting at 1? Really? Why did you guys think that this is a good idea? Not to mention the mixed approach you&#039;ve taken for delimiting blocks of code. I mean, there are a few schools of thought on this, and from the perspective of the programmer, you have two main choices: using a symbol (usually the curly braces; parenthesis in LISP or Prolog or perhaps a few other purely functional languages), which has the disadvantage of being hard to figure out (without tooling) to which code block a closing curly brace/parenthesis belongs to. Or there is the horrible — but useful! — idea of using reverse keywords to begin and start code blocks, e.g. &amp;lt;code&amp;gt;if ... fi&amp;lt;/code&amp;gt;, which are aesthetically ugly but they do the job well. Alternatively, you always have &amp;lt;code&amp;gt;if ... endif&amp;lt;/code&amp;gt; and similar constructs — giving the code some structure at the expense of having to type more. But &amp;lt;code&amp;gt;if... end&amp;lt;/code&amp;gt; (and everything else ending with, well, &amp;lt;code&amp;gt;end&amp;lt;/code&amp;gt;...) just means typing 3 characters instead of just one, but with the same lack of semantics. If you see an &amp;lt;code&amp;gt;end&amp;lt;/code&amp;gt; in Lua, it can belong to &amp;lt;em&amp;gt;anything&amp;lt;/em&amp;gt;, so it has no semantical meaning by itself (unlike a &amp;lt;code&amp;gt;fi&amp;lt;/code&amp;gt; which can &#039;&#039;only&#039;&#039; terminate a conditional block started with &amp;lt;code&amp;gt;if&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
That said (and I wish I could have added the sentence &amp;quot;besides all the above, the rest is fine!&amp;quot;, but I cannot — the list of oddities and quirks in Lua go on and on), the whole point of &amp;quot;Lua&amp;quot; is that it&#039;s not &#039;&#039;just&#039;&#039; the programming language. That&#039;s what the developer interacts with. Lua, however, is worthless by itself; the concept &#039;&#039;always&#039;&#039; includes the notion of an interpreter/JIT/full compiler that runs embedded into an application. And &#039;&#039;that&#039;s&#039;&#039; where Lua shines: it may have its oddities, but it was &#039;&#039;designed&#039;&#039; for having all these oddities (and so much more) getting efficiently and quickly run by any &#039;host&#039;. That also means the ease of passing information between the host application and the Lua engine, in a safe (and sandboxed!) way. Again: you &#039;&#039;could&#039;&#039; do it on any other language via its own embedded mechanism. It&#039;s just that Lua happens to be especially good at such interfaces. I can imagine that getting Lua to access the vast library of LSL functionality is an almost trivial exercise; it&#039;s &#039;&#039;supposed&#039;&#039; to be simple to do that, after all. In other words: it&#039;s often less about the end users (those that will ultimately write code in Lua) and much more about the environment surrounding Lua, and how its highly portable embedded engine interfaces with such an environment.&lt;br /&gt;
&lt;br /&gt;
Perhaps ironically, around 2020 or so, I struggled with the same issues, although at a much tinier (and less important!) scale. While developing some code that allowed full control over groups of independent agents (let&#039;s call them &#039;bots), I went through the usual phases of sloppy programming: first, figure out what functionality is common to all (say, logging in, moving around...) and implement those. Then essentially have a list (or a database table) with all the independent agents, and, for each, follow a sequence of operations, all of which hard-coded, That was fine, until the edge cases started to pop in — such as different agents requiring different settings. That quickly became unmanageable, so the next approach was to use more fields on the table, such as keeping track of the agent&#039;s location. To deal with different orders of execution, another table was created, listing, for each agent, what the sequence of operations for that agent was instructed to perform. This essentially describes a very crude state machine, where the entries are indexed by the function call to be made next.&lt;br /&gt;
&lt;br /&gt;
And then, the inevitable happens. How to deal with conditionals? How to deal with loops? Oh, and wouldn&#039;t it be great to have some place to store some results separately for each function? (it could be &amp;quot;registers&amp;quot;, or a classic stack with push/pop operations...)&lt;br /&gt;
&lt;br /&gt;
In essence, I was asking the wrong questions: what I needed wasn&#039;t to &amp;quot;reinvent the wheel&amp;quot; and creating what ultimately would become an embedded language, designed from scratch. Instead, what I &#039;&#039;really&#039;&#039; needed was an embedded language!&lt;br /&gt;
&lt;br /&gt;
At that time, I wanted just something &#039;&#039;very simple&#039;&#039; which could be &#039;&#039;quickly&#039;&#039; implemented (and its functions mapped to those on the host application). Since I usually work (on my own) with Go, I started looking for Go-specific solutions. And sure, there are a few, but they have the disadvantage that they&#039;re even less known (or used) than LSL. The obvious choice was naturally embedding a JavaScript engine, of which the best of the best is, allegedly, Google&#039;s own. It is also... massive, to say the least. Never mind that our contemporary Chromium-based browsers use this massive engine (even the SL Viewer does!); it might make sense if you have a need to access the vast library of JavaScript code all over the place; but that wasn&#039;t my case. I just wanted something &#039;&#039;familiar&#039;&#039; and having a community of a decent number of people that are able to provide libraries and have some availability to answer questions. That&#039;s when I came up with the idea of testing out Lua. There was a semi-decent effort to write a Lua engine completely in Go (as opposed to linking it to an externally-compiled C/C++ library, which would be the &amp;quot;usual&amp;quot; choice for most solutions), and I thought it would be a nice idea to experiment with.&lt;br /&gt;
&lt;br /&gt;
As it happens, there are a few people writing some helper functions on top of what the &amp;quot;raw&amp;quot; library supports, which make exposing functions to Lua (and vice-versa) an almost banal exercise. It works admirably well. In fact, the &#039;&#039;only&#039;&#039; thing that really works &#039;&#039;well&#039;&#039; in my meagre coding attempts are the bits running on Lua — the rest of the much more complex effort of integrating the core application with the control of the independent agents is (for me!) much, much harder. In other words: by using Lua as the choice for the embedded language, I solved one problem — the user-facing aspect of dealing with the actual agent scripting, using a high-level language — and didn&#039;t need to ever worry with it again. It just works. Now I can switch the focus to &amp;quot;everything else&amp;quot; instead.&lt;br /&gt;
&lt;br /&gt;
I was super-excited (and to be truthful, I had never programmed in Lua before, and know little more than the basics; think of my level of experience as being that of a 12-year-old who learned Lua to do interactive objects in Roblox — that&#039;s pretty much how much I know) and astonished at how well this worked. Not in terms of performance (frankly, I didn&#039;t really make benchmarks — all the bottlenecks occur &#039;&#039;outside&#039;&#039; the Lua environment, anyway; Lua scripts are spending most of their time waiting for the rest of the code to produce results and feed them back to Lua — but even the Go developers who did this Lua engine port admit that it&#039;s not &#039;&#039;that&#039;&#039; fast or optimised), but rather in terms of &#039;&#039;integration&#039;&#039;. Now, nobody knows how the LL server code looks like, but we can at least assume that it shares some vague similarities with OpenSimulator code, for the simple reason that both technologies needs to handle functions called by LSL. While there are potentially many ways to implement that, and nobody can know if LL&#039;s code uses the &amp;quot;same&amp;quot; way as OpenSimulator (while, of course, the reverse is true!), it&#039;s worth speculating about the trouble in making &#039;&#039;&#039;every library function&#039;&#039;&#039; accessible from Lua as well. My belief is that this &#039;&#039;will&#039;&#039; be little more than a trivial exercise; compiling LSL to the Luau VM will be much more challenging, I think :)&lt;br /&gt;
&lt;br /&gt;
That said, looking back, it&#039;s obvious that I shouldn&#039;t have been so &amp;quot;surprised&amp;quot; that everything worked so well. &#039;&#039;&#039;Lua is &#039;&#039;supposed&#039;&#039; to be embedded.&#039;&#039;&#039; It&#039;s one of its design features. In fact, although you &#039;&#039;can&#039;&#039; run Lua from the command line (or even compile things into executables), there is little incentive to do so: Lua will not be especially good, or fast, or &amp;quot;trendy&amp;quot;, for independent, self-contained, command-line utilities. For that you already have C/C++, Python, and more recently Rust, Node.js and a bit of Go. That pretty much covers all the needs. On the other hand, the increasing number of applications using Lua as an &#039;&#039;embedded&#039;&#039; language is frankly astonishing. On my &#039;personal&#039; bare metal server hosted on a remote data center, I have been running software that I never thought that they included an embedded Lua engine — such as one of the anti-spam services (!). Even the webserver itself (the lightweight nginx) can now have a module to allow its configuration files to be Lua programmes, as opposed to, well, configuration files.&lt;br /&gt;
&lt;br /&gt;
I also have no idea, in the battle for the embedded programming languages, where Lua stands against the &amp;quot;competition&amp;quot;. These days, even though LL tested out a very impressive array of choices, the truth is that JavaScript is highly likely the &#039;&#039;most&#039;&#039; used embedded language, since &#039;&#039;every&#039;&#039; browser uses it, and that means close to 5 billion users (who, of course, only &#039;&#039;benefit&#039;&#039; from JavaScript, they don&#039;t interact with it in any way). Python was once quite popular as an embedded language, but I guess that its maintenance chores are a nuisance to deal with. LISP was quite popular as an embedded language, and has probably been made famous because it used to be the embedded language used by Richard Stallman&#039;s Unix-based Emacs as well as Autodesk&#039;s AutoCAD, each running their specific dialects of LISP. Because of the widespread usage of LISP in the early days of embedded languages, I can imagine that it&#039;s still more popular than many other solutions out there. The only trouble with LISP is that it requires a very specific mindset to work with it (or else all things AI-related would be written in LISP and not in Python, which is hardly the &amp;quot;best&amp;quot; choice for that — but who am I to contradict millions of users?)&lt;br /&gt;
&lt;br /&gt;
That said...&lt;br /&gt;
&lt;br /&gt;
And that was the longest introduction ever...&lt;br /&gt;
&lt;br /&gt;
I have a few questions that the original article didn&#039;t explain!&lt;br /&gt;
&lt;br /&gt;
# Will SL-Lua also support external libraries (&amp;quot;external&amp;quot; meaning: any other Lua-asset to which we have permissions to access)? This is a crucial requirement, which LSL does not satisfy in the least; but it would make millions of lines of LSL code irrelevant, to the great relief of those needing to support the ever-increasing complexity put into LSL to allow it to do very complex tasks indeed. Imagine, for instance, a HUD library that can be easily distributed, with which 90% of the functionality would be packaged inside the library; you&#039;d only need to add a bit of &amp;quot;glue code&amp;quot; on top of that. Or imagine the same thing for vendor machines — such as Casper&#039;s — which could therefore have a much cleaner way of integrating with &amp;quot;external&amp;quot; scripts, without needing to expose anything.&lt;br /&gt;
# In the same vein: except for the cases mentioned in the main article, does this mean that LL will essentially provide all (or most) standard libraries for Lua? This is relevant for those who really don&#039;t want to reinvent the wheel; the Lua standard libraries already do a &#039;&#039;lot&#039;&#039; of useful work, there is no need to redo everything — so long as these libraries can be used, of course. Even with limitations. It also means that a lot of LSL functionality which LL had to develop from scratch might not be necessary as well (when &#039;&#039;LSL&#039;&#039; needs access to those specific functions, it would just call them — via Lua!). I&#039;m thinking about things such as the math library functions, for instance.&lt;br /&gt;
# Still on the issue of libraries: the article talks about a &amp;lt;code&amp;gt;ll&amp;lt;/code&amp;gt; namespace, which in Lua can have different meanings and implementations (&amp;quot;everything is a table&amp;quot;). Does that imply that there will be &#039;&#039;one&#039;&#039; LL library with &#039;&#039;all&#039;&#039; the functions we have today for LSL — or that the LL library will be split according to different kinds of functionality, and just loaded on demand if needed? I&#039;m thinking about how worthless it is for me to know that there are functions for dealing with Pathfinding, or all the vehicles and physics systems, when all I&#039;ve got is an Animation Overrider to worry about. A much cleaner way to do things would be to encapsulate related functionality under different libraries or objects. Maybe that&#039;s exactly what is going to be done, and that&#039;s what will happen &amp;quot;under the hood&amp;quot; when we call a Lua function with &amp;lt;code&amp;gt;ll.someFunction()&amp;lt;/code&amp;gt;. I&#039;m not sufficiently familiar with Lua to know if there is a way to do that or not. My reason for asking that is just because I see the vastness of never-ending functions getting a bit out of hand, and it would be far better to aggregate them under different compartments — be they namespaces, libraries, tables/objects, whatever fits the concept best. The end-user (we, the scripters!) will probably never need to worry about that. Or maybe they would — by explicitly calling the libraries they need. This would also allow for more error-checking, i.e. you won&#039;t be able to call a similarly-named function by mistake if you have checked in a different library. A typical example are the two key/value pair stores, one implemented in one way for Experiences, the other implemented for any script running in a prim. Sometimes, it&#039;s easy to forget which is which, but if I haven&#039;t checked in the Experiences library explicitly, I will get an error for a &amp;quot;missing function&amp;quot; — a good hint that I&#039;m probably trying to do something which isn&#039;t intended.&lt;br /&gt;
# Will Lua scripts be versioned, i.e. will there be a way to automatically let the programmer release a new version, and all existing copies of that asset would automatically &amp;quot;migrate&amp;quot; to the new version instead? This is becoming a more important requirement all the time, as the complexity of scripts continues to increase, and the need to get rid of obsolete versions and replace them automagically is a must; however, I&#039;m quite aware that only a fraction of the users actually bother to do the upgrades. That happens for several reasons, but I can imagine that a lot has to do with the whole complex procedure of getting an &amp;quot;upgrade box&amp;quot;, rezzing it on the ground (assuming you&#039;ve got the correct permissions), then do the same with your HUD, clothing, device, etc., activate the upgrade box, and hope that it works...&lt;br /&gt;
# And how about a (better) integration with an &#039;&#039;external&#039;&#039; debugger? Having an internal one, of course, is great effort that you&#039;ve done (or are in the process of doing). But let&#039;s just say that the internal code editor is not really up to modern standards... that said, I wish that the integration wt &lt;br /&gt;
# Will you include an official Lua &amp;lt;abbr title=&amp;quot;Language Server Protocol&amp;quot;&amp;gt;LSP&amp;lt;/abbr&amp;gt; server? (There are plenty of those around! It&#039;s just that none will come with LL&#039;s library functions,  of course...). This is mostly for those using external editors, since, internally, you already have something similar for LSL (similar... not quite the same, though...)&lt;br /&gt;
# While I understand that, for several reasons, mixing and matching code from LSL and Lua is, in general, a Bad Thing™, I wonder if you guys have thought about the possibility of LSL to call functions in Lua. This is mostly important if the Lua support allows external libraries (which could be Lua scripts) as described above; this could be a way to allow LSL to be &amp;quot;expanded&amp;quot; with libraries as well (namely, with Lua libraries, that is). That might matter most during a transition phase, of course.&lt;br /&gt;
# Some people that I talked to about Lua, who are themselves not programmers, asked me if, with Lua, there could be an (embedded) visual programming editor, such as you have in, say, Blender (or any other complex application, especially in the 3D rendering business; but even Filter Forge filters are visually programmed that way). While such options certainly exist and could be deployed over whatever Lua-based infrastructure, the question is if LL has considered that possibility at some point?&lt;br /&gt;
# And finally, just for the sake of curiosity, will the prevailing conceptual model for Lua also be a glorified state machine, like LSL? Or will a completely different model? The state machine model was actually a quite clever concept, even though most scripts I&#039;ve come across rarely employ more than 1-2 states, which defeats the purpose. I&#039;d think that the move to Lua would imply the end of the state machine model; but, on the other hand, this would also mean a much harder effort in adapting old LSL scripts and converting them to Lua (manually, that is!). Thus my curiosity!&lt;br /&gt;
&lt;br /&gt;
That said, I&#039;m looking forward to see what you guys have come up with :)&lt;br /&gt;
&lt;br /&gt;
— [[User:Gwyneth Llewelyn|Gwyneth Llewelyn]] ([[User talk:Gwyneth Llewelyn|talk]]) 02:32, 1 September 2024 (PDT)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== I like Lua a lot! (compared to LSL). Here are some more thoughts and questions: ==&lt;br /&gt;
As a scripting instructor, my focus is on how to effectively teach Lua, particularly to those whose main language is LSL, and the challenges that they may encounter while learning and practicing Lua. I plan to start teaching Lua as soon as it becomes available on the beta grid.&lt;br /&gt;
&lt;br /&gt;
: - Lua often has multiple ways to achieve the same result. Will you be providing guidelines or best practices to ensure a consistent approach? With thousands of scripters learning Lua, teaching them consistently would be highly beneficial.&lt;br /&gt;
&lt;br /&gt;
: - One challenge for beginners is Lua&#039;s use of dynamic variables without assigned types, which can lead to issues like typos in variable names and unintended mixing of types. It would be helpful if the Lua editor or a pre-processor could include features such as:&lt;br /&gt;
# Warning if a variable name appears only once in the script, as this might indicate a typo.&lt;br /&gt;
# Warnings about undeclared variables. For instance, adding the &amp;quot;declaration&amp;quot; of global variables using a comment like: -- global variableName, anotherVariable, etc., to make it possible.&lt;br /&gt;
# Warnings about type mixing. For instance, adding prefixes like: nCounter, sMessage, bIsOn, or xMultiType (with &amp;quot;x&amp;quot; for variables expected to hold multiple types), to make it possible.&lt;br /&gt;
&lt;br /&gt;
: At least, implementing the option 1 could be not too difficult and extremely helpful.&lt;br /&gt;
&lt;br /&gt;
: - Will there be an option to translate from LSL user-code to Lua user-code? This would be incredibly valuable for reusing existing LSL scripts without manually rewriting them in Lua.&lt;br /&gt;
&lt;br /&gt;
: - The only aspect of Lua that I dislike is the use of ~= (inequality operator instead of !=). On some keyboards requires multiple key presses  (in mine, for instance, is AltGr+4+spacebar). It would be convenient if the editor could, when we type !=, replace it with ~=.&lt;br /&gt;
&lt;br /&gt;
Expecting to see Lua in the beta grid in a few months :)&lt;br /&gt;
&lt;br /&gt;
-- [[User:SuzannaLinn Resident|SuzannaLinn Resident]] ([[User talk:SuzannaLinn Resident|talk]]) 09:34, 10 September 2024 (PDT)&lt;br /&gt;
&lt;br /&gt;
: I&#039;d recommend a JSDoc style of comments for the Lua code rather than using a complex naming convention to variables. I&#039;ve never seeng the single letter typing convention pass the test of time.&lt;br /&gt;
: It was mentioned in a recent user meeting that there will be no transpiler for LSL-&amp;gt;Lua code. You will still be able to write LSL like normal, you don&#039;t have to use Lua. LSL is fine, no need to convert scripts. Link messages, listeners and all will also still work between different types of scripts. So you could send a link message from an LSL script to a Luau script.&lt;br /&gt;
: Linting would be good but there are linters already out there. See extensions in Visual Studio Code, there are some that provide lint and type warnings for Luau. I&#039;m not sure the script editor in the SL client will ever do more than the minimum, if anything I&#039;d like better integration with external editors, such as opening up the external editor directly on double-clicking a script instead of the script editor window. -- [[User:Nexii Malthus|&amp;lt;span style=&amp;quot;color: #111; text-shadow:-1px -1px #ddd, 1px 1px #888;&amp;quot;&amp;gt;Nexii Malthus&amp;lt;/span&amp;gt;]] 23:34, 13 September 2024 (PDT)&lt;br /&gt;
&lt;br /&gt;
After further investigation and asking at the SUG meetings, I have some answers:&lt;br /&gt;
: - LuaSL will have the same type-checking and linting capabilities as Luau:&lt;br /&gt;
:: - 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 stricter than LSL.&lt;br /&gt;
:: - 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.&lt;br /&gt;
:- There are no plans for a transpiler from LSL to Lua. Since LSL scripts can remain in LSL, the need for a transpiler is not significant enough (as previously commented). But not in my case, as a scripting teacher: I have around 60 classes with LSL scripts that I will need to change to Lua, so I will be working in my own transpiler.&lt;br /&gt;
-- [[User:SuzannaLinn Resident|SuzannaLinn Resident]] ([[User talk:SuzannaLinn Resident|talk]]) 07:38, 18 September 2024 (PDT)&lt;br /&gt;
&lt;br /&gt;
I&#039;m gathering information about Lua on my userpage for my students and anyone interested in it: &#039;&#039;&#039;[[User:SuzannaLinn_Resident/Lua | Introducing Lua index page]]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
-- [[User:SuzannaLinn Resident|SuzannaLinn Resident]] ([[User talk:SuzannaLinn Resident|talk]]) 05:52, 3 October 2024 (PDT)&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1218094</id>
		<title>Lua FAQ</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1218094"/>
		<updated>2025-03-21T17:12:18Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Signal Linden moved page Lua FAQ to SLua FAQ&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
=== Can I keep using LSL? ===&lt;br /&gt;
&lt;br /&gt;
Yep! LSL should work exactly the same as before, and a lot of testing work has been done (and is being done) to ensure that is the case. Even if you’re an LSL pro and rely on things like &amp;lt;code&amp;gt;(some_list != [])&amp;lt;/code&amp;gt; for getting the list length, or strange things like &amp;lt;code&amp;gt;((largestring=&amp;quot;&amp;quot;) + largestring)&amp;lt;/code&amp;gt;, you’re fully covered. Those still work, and now have optimized implementations.&lt;br /&gt;
&lt;br /&gt;
In fact, LSL will get better than ever before, running faster, and using less memory! The generated bytecode for an LSL script in Lua is generally half the size of a Mono script, and most strings take up half as much memory by virtue of using UTF-8 instead of UTF-16.&lt;br /&gt;
&lt;br /&gt;
We’ve taken a lot of care in writing the new LSL compiler to make the scripting experience for people who prefer LSL better than it was before.&lt;br /&gt;
&lt;br /&gt;
=== Are you going to stop adding functions / events to LSL? ===&lt;br /&gt;
&lt;br /&gt;
In the general case, no. A big part of the work we’ve done is making it easy to share functions between LSL and Lua without much extra effort. For events and functions that can be reasonably exposed to LSL (i.e., they only use simple types that LSL has anyway) then LSL should still get those.&lt;br /&gt;
&lt;br /&gt;
For more advanced features, the idea is that there should still be a path for exposing them to LSL. Obviously, with Lua it’s possible to have object-oriented APIs that would not be possible in LSL, but those objects will generally be written in a way that calling a method on a “special” object we introduce in Lua will really boil down to a function call that can be expressed in LSL.&lt;br /&gt;
&lt;br /&gt;
Not a real example, but &amp;lt;code&amp;gt;some_object:giveInventoryList(&amp;quot;folder_name&amp;quot;, inventory_list)&amp;lt;/code&amp;gt; might really boil down to a &amp;lt;code&amp;gt;llGiveInventoryList(some_object_id, &amp;quot;folder_name&amp;quot;, &amp;quot;inventory_list&amp;quot;);&amp;lt;/code&amp;gt; call. In that way, most additional Lua functionality will be a fancy “easier” wrapper around functions shared with LSL!&lt;br /&gt;
&lt;br /&gt;
The caveat is if functions are added that use new types that aren&#039;t in LSL (like functions that take in a &amp;lt;code&amp;gt;key -&amp;gt; value&amp;lt;/code&amp;gt; map) likely won&#039;t be exposed to LSL, as there&#039;s no way to represent them in LSL anyway.&lt;br /&gt;
&lt;br /&gt;
=== Wait, LSL can run on Lua now? How? Why would you do that? ===&lt;br /&gt;
&lt;br /&gt;
A reasonable question! It sounds crazy at first, but as it turns out, LSL maps really well to Lua bytecode, and we already have a compiler that can output both Mono and legacy LSO2 bytecode. Writing a compiler that could output Lua bytecode wasn&#039;t terribly difficult, and it allows LSL scripts to run faster, and be more memory-efficient. Most importantly, it allows us to test behavior of the new scripting engine against the behavior of the existing Mono scripting engine. This helps us make sure we&#039;re doing an apples-to-apples comparison on our benchmarks, and that all the nice things that Mono currently gets us will also work correctly under the new scripting engine.&lt;br /&gt;
&lt;br /&gt;
Additionally, for better or for worse, LSL is here to stay. People have been writing scripts in it for 20 years, and they&#039;re going to be running on the servers for years to come. Performance and memory usage issues with LSL scripts will continue to matter for the foreseeable future. The faster the server can run existing LSL scripts, the more time it can dedicate to running newer Lua scripts. Memory usage has been a particular problem with the existing Mono scripting engine, and this would help us address this.&lt;br /&gt;
&lt;br /&gt;
=== What about running scripts on the viewer-side if I want viewer-side effects? Does this have any interaction with the work adding Lua addons into the viewer? ===&lt;br /&gt;
&lt;br /&gt;
No, the work on adding Lua addons to the viewer is a separate effort. Lua scripts on SL will strictly run on the region, and not on the people&#039;s viewers. We&#039;re focused on providing a high-quality replacement to the existing server-side scripts before we look at anything else.&lt;br /&gt;
&lt;br /&gt;
Note that we are all too aware of the current difficulty of scripting HUD UIs for Second Life (we make a lot of our own) and we&#039;re thinking of ways we can make things better, but there are no definite plans, and it wouldn&#039;t involve running scripts on the viewer in the near-term. The current UI tooling exposed to Lua viewer addons isn&#039;t tooled for being called from untrusted code either way, so lack of &amp;quot;client-side&amp;quot; scripts is unlikely to hinder any effort to create a better API for HUDs for in-world systems.&lt;br /&gt;
&lt;br /&gt;
=== You said Lua runs LSL faster than Mono? It uses less memory? How? I&#039;m suspicious. ===&lt;br /&gt;
&lt;br /&gt;
A reasonable suspicion! We were also suspicious and suspected that Lua would be slightly slower, but in reality, yes, Lua can run LSL scripts quite a bit faster than the custom-patched Mono 2.6 we currently use. The best LSL on Mono has performed compared to Luau in our internal tests is 10% slower than Luau. Similarly, most LSL on Luau scripts end up being around half the size of Mono scripts. That means much more memory to play with in your LSL scripts.&lt;br /&gt;
&lt;br /&gt;
The reason why isn&#039;t intuitive, after all, Mono is a lot more intricate and has a lot of fancy performance optimizations like a JIT optimizer, but it becomes clear when you look at the bytecode that&#039;s generated for LSL on each scripting engine.&lt;br /&gt;
&lt;br /&gt;
Consider the following LSL function as an example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lsl2&amp;quot;&amp;gt;&lt;br /&gt;
float loudSubtract(float val, float sub) {&lt;br /&gt;
    // subtract, telling the owner what we did&lt;br /&gt;
	float new_val = val - sub;&lt;br /&gt;
	llOwnerSay(&amp;quot;New val is &amp;quot; + (string)new_val);&lt;br /&gt;
	return new_val;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This compiles to the following Luau bytecode (subject to further optimization):&lt;br /&gt;
&lt;br /&gt;
{| role=&amp;quot;presentation&amp;quot; class=&amp;quot;wikitable mw-collapsible autocollapse mw-collapsed&amp;quot;&lt;br /&gt;
| &amp;lt;strong&amp;gt;Lua bytecode&amp;lt;/strong&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre&amp;gt;&lt;br /&gt;
Function 0 (_floudSubtract):&lt;br /&gt;
SUB R2 R0 R1&lt;br /&gt;
GETIMPORT R3 2 [ll.OwnerSay]&lt;br /&gt;
GETIMPORT R6 5 [lsl.cast]&lt;br /&gt;
MOVE R7 R2&lt;br /&gt;
LOADN R8 3&lt;br /&gt;
CALL R6 2 1&lt;br /&gt;
LOADK R5 K6 [&#039;New val is &#039;]&lt;br /&gt;
CONCAT R4 R5 R6&lt;br /&gt;
CALL R3 1 0&lt;br /&gt;
RETURN R2 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Okay, that&#039;s pretty decent. Some places where things could be more optimized, but no big deal. The equivalent Mono bytecode is (and bear with us, this is long:)&lt;br /&gt;
&lt;br /&gt;
{| role=&amp;quot;presentation&amp;quot; class=&amp;quot;wikitable mw-collapsible autocollapse mw-collapsed&amp;quot;&lt;br /&gt;
| &amp;lt;strong&amp;gt;Mono bytecode&amp;lt;/strong&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre&amp;gt;&lt;br /&gt;
 .class public auto ansi serializable beforefieldinit&lt;br /&gt;
  LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    extends [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 &#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 val&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; l1&#039;&lt;br /&gt;
&lt;br /&gt;
  .method compilercontrolled specialname rtspecialname instance void&lt;br /&gt;
    .ctor(&lt;br /&gt;
      int32 _param1,&lt;br /&gt;
      class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 _param2,&lt;br /&gt;
      float32 _param3,&lt;br /&gt;
      float32 _param4,&lt;br /&gt;
      float32 _param5,&lt;br /&gt;
      int32 _param6&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 4&lt;br /&gt;
&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: call         instance void [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame::.ctor()&lt;br /&gt;
&lt;br /&gt;
    // [29 5 - 29 28]&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldarg.s      _param1&lt;br /&gt;
    IL_0009: nop&lt;br /&gt;
    IL_000a: nop&lt;br /&gt;
    IL_000b: nop&lt;br /&gt;
    IL_000c: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
    // [30 5 - 30 34]&lt;br /&gt;
    IL_0011: ldarg.0      // this&lt;br /&gt;
    IL_0012: ldarg.s      _param2&lt;br /&gt;
    IL_0014: nop&lt;br /&gt;
    IL_0015: nop&lt;br /&gt;
    IL_0016: nop&lt;br /&gt;
    IL_0017: stfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 23]&lt;br /&gt;
    IL_001c: ldarg.0      // this&lt;br /&gt;
    IL_001d: ldarg.s      _param3&lt;br /&gt;
    IL_001f: nop&lt;br /&gt;
    IL_0020: nop&lt;br /&gt;
    IL_0021: nop&lt;br /&gt;
    IL_0022: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
&lt;br /&gt;
    // [32 5 - 32 23]&lt;br /&gt;
    IL_0027: ldarg.0      // this&lt;br /&gt;
    IL_0028: ldarg.s      _param4&lt;br /&gt;
    IL_002a: nop&lt;br /&gt;
    IL_002b: nop&lt;br /&gt;
    IL_002c: nop&lt;br /&gt;
    IL_002d: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
    // [33 5 - 33 28]&lt;br /&gt;
    IL_0032: ldarg.0      // this&lt;br /&gt;
    IL_0033: ldarg.s      _param5&lt;br /&gt;
    IL_0035: nop&lt;br /&gt;
    IL_0036: nop&lt;br /&gt;
    IL_0037: nop&lt;br /&gt;
    IL_0038: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
    // [34 5 - 34 28]&lt;br /&gt;
    IL_003d: ldarg.0      // this&lt;br /&gt;
    IL_003e: ldarg.s      _param6&lt;br /&gt;
    IL_0040: nop&lt;br /&gt;
    IL_0041: nop&lt;br /&gt;
    IL_0042: nop&lt;br /&gt;
    IL_0043: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0048: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor&lt;br /&gt;
&lt;br /&gt;
  .method public virtual instance object&lt;br /&gt;
    Resume() cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 8&lt;br /&gt;
&lt;br /&gt;
    // [37 37 - 37 99]&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: ldfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
    IL_000c: ldarg.0      // this&lt;br /&gt;
    IL_000d: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
    IL_0012: call         instance float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575::gloudSubtract(float32, float32)&lt;br /&gt;
    IL_0017: box          [mscorlib]System.Single&lt;br /&gt;
    IL_001c: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::Resume&lt;br /&gt;
} // end of class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 // ... Within the actual script class&lt;br /&gt;
&lt;br /&gt;
 .method public hidebysig instance float32&lt;br /&gt;
    gloudSubtract(&lt;br /&gt;
      float32 val,&lt;br /&gt;
      float32 &#039;sub&#039;&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 16&lt;br /&gt;
    .locals init (&lt;br /&gt;
      [0] float32 num1,&lt;br /&gt;
      [1] int32 &#039;l1[34-35], num2[69-151], num2[151-152]&#039;,&lt;br /&gt;
      [2] class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame c9eec9e37575gloudSubtractFrame,&lt;br /&gt;
      [3] float32 num3&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    // [17 5 - 17 60]&lt;br /&gt;
    IL_0000: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsRestoring()&lt;br /&gt;
    IL_0005: brfalse      IL_0036&lt;br /&gt;
&lt;br /&gt;
    // [19 7 - 19 202]&lt;br /&gt;
    IL_000a: call         class [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame [UThread]LindenLab.SecondLife.UThread.UThread::Pop()&lt;br /&gt;
    IL_000f: castclass    LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0014: stloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
    // [20 7 - 20 53]&lt;br /&gt;
    IL_0015: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0016: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
    IL_001b: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [21 7 - 21 55]&lt;br /&gt;
    IL_001c: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_001d: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0022: stloc.1      // l1&lt;br /&gt;
&lt;br /&gt;
    // [22 7 - 22 55]&lt;br /&gt;
    IL_0023: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0024: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
    IL_0029: switch       (IL_0085, IL_004b)&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 58]&lt;br /&gt;
    IL_0036: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_003b: brfalse      IL_004b&lt;br /&gt;
&lt;br /&gt;
    // [33 7 - 33 15]&lt;br /&gt;
    IL_0040: ldc.i4       1 // 0x00000001&lt;br /&gt;
    IL_0045: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [34 7 - 34 20]&lt;br /&gt;
    IL_0046: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [37 5 - 37 70]&lt;br /&gt;
    IL_004b: ldarg.s      &#039;sub&#039;&lt;br /&gt;
    IL_004d: nop&lt;br /&gt;
    IL_004e: nop&lt;br /&gt;
    IL_004f: nop&lt;br /&gt;
    IL_0050: ldarg.s      val&lt;br /&gt;
    IL_0052: nop&lt;br /&gt;
    IL_0053: nop&lt;br /&gt;
    IL_0054: nop&lt;br /&gt;
    IL_0055: call         float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&lt;br /&gt;
    IL_005a: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [38 5 - 38 84]&lt;br /&gt;
    IL_005b: ldloc.0      // num1&lt;br /&gt;
    IL_005c: call         string [LslLibrary]LindenLab.SecondLife.LslRunTime::ToString(float32)&lt;br /&gt;
    IL_0061: ldstr        &amp;quot;New val is &amp;quot;&lt;br /&gt;
    IL_0066: call         string [LslUserScript]LindenLab.SecondLife.LslUserScript::Add(string, string)&lt;br /&gt;
    IL_006b: call         void [LslLibrary]LindenLab.SecondLife.Library::llOwnerSay(string)&lt;br /&gt;
&lt;br /&gt;
    // [39 5 - 39 58]&lt;br /&gt;
    IL_0070: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_0075: brfalse      IL_0085&lt;br /&gt;
&lt;br /&gt;
    // [41 7 - 41 15]&lt;br /&gt;
    IL_007a: ldc.i4       0 // 0x00000000&lt;br /&gt;
    IL_007f: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [42 7 - 42 20]&lt;br /&gt;
    IL_0080: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [45 5 - 45 17]&lt;br /&gt;
    IL_0085: ldloc.0      // num1&lt;br /&gt;
    IL_0086: ret&lt;br /&gt;
    IL_0087: nop&lt;br /&gt;
&lt;br /&gt;
    // [47 5 - 47 194]&lt;br /&gt;
    IL_0088: ldloc.1      // num2&lt;br /&gt;
    IL_0089: ldarg.0      // this&lt;br /&gt;
    IL_008a: ldarg        val&lt;br /&gt;
    IL_008e: nop&lt;br /&gt;
    IL_008f: nop&lt;br /&gt;
    IL_0090: ldarg        &#039;sub&#039;&lt;br /&gt;
    IL_0094: nop&lt;br /&gt;
    IL_0095: nop&lt;br /&gt;
    IL_0096: ldloc.0      // num1&lt;br /&gt;
    IL_0097: ldloc.1      // num2&lt;br /&gt;
    IL_0098: newobj       instance void LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor(int32, class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575, float32, float32, float32, int32)&lt;br /&gt;
    IL_009d: pop&lt;br /&gt;
&lt;br /&gt;
    // [49 5 - 49 17]&lt;br /&gt;
    IL_009e: ldloc.3      // num3&lt;br /&gt;
    IL_009f: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
While this might look unreasonably large, this is also pretty similar to how &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt; C# functions that have &amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt;s get compiled. The code required to allow a function to pause in the middle to let other things run is necessarily very large, and our Mono bytecode instrumentation is essentially a special variant of that which optimizes for cases where the function won&#039;t have to pause. Try pasting the following code into sharplab.io (while decompiling to IL) and you&#039;ll see something very similar:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
using System.Threading.Tasks;&lt;br /&gt;
public class Example {&lt;br /&gt;
    public async Task llOwnerSay(string val)&lt;br /&gt;
    {&lt;br /&gt;
        // stub&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    public async Task&amp;lt;float&amp;gt; loudSubtract(float val, float sub) {&lt;br /&gt;
        float new_val = val - sub;&lt;br /&gt;
        await llOwnerSay(&amp;quot;New val is &amp;quot; + new_val.ToString());&lt;br /&gt;
        return new_val;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Lua bytecode ends up being a lot smaller, because the Lua scripting engine itself knows how to handle this case, and doesn&#039;t need to add any special code to your scripts to allow them to be paused and resumed in the middle of a function.&lt;br /&gt;
&lt;br /&gt;
Even though Mono has a JIT optimizer, which should in theory make the script run faster, this sort of code is very unusual, and the JIT optimizer isn&#039;t able to optimize it very well. That means that even though Luau itself is in theory slower, it&#039;s able to run LSL much faster than Mono can. &lt;br /&gt;
&lt;br /&gt;
You might also notice things like call &amp;lt;code&amp;gt;float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&amp;lt;/code&amp;gt; in the Mono code. Those are necessary because of LSL&#039;s weird operand evaluation order, but are unnecessary in Luau because of the more efficient form of bytecode that Luau uses.&lt;br /&gt;
&lt;br /&gt;
In general, C# doesn&#039;t aim at producing very small bytecode for async functions, which is important for something like Second Life where you might have thousands of different individual scripts running within a region. Luau does generate small bytecode, while still having decent performance, making it a good choice for Second Life. And hey, strings are UTF-8 in Lua instead of UTF-16 as in Mono, so they use much less memory for most data as well.&lt;br /&gt;
&lt;br /&gt;
=== Are we going to get a higher memory limit in scripts? ===&lt;br /&gt;
&lt;br /&gt;
Not at first, Luau uses less memory than Mono, so you shouldn&#039;t really miss it. You&#039;re not going to start hitting script memory limits just because you converted your script to Lua. Using Lua proper should allow you to more precisely control memory usage in your scripts, e.g. by being able to handle memory allocation errors in &amp;lt;code&amp;gt;pcall()&amp;lt;/code&amp;gt; and access to the mutable &amp;lt;code&amp;gt;buffer&amp;lt;/code&amp;gt; class for byte-packing.&lt;br /&gt;
&lt;br /&gt;
There is some interest in giving specific scripts a little more memory if they&#039;re designated &amp;quot;high priority&amp;quot;, like a main &amp;quot;game manager&amp;quot; object for an experience, but there are no specific plans for this at the moment.&lt;br /&gt;
&lt;br /&gt;
=== Are my existing LSL scripts going to run on Lua Automatically? ===&lt;br /&gt;
&lt;br /&gt;
While we can test running LSL in Lua now, and the idea is to eventually phase out Mono, there’s no immediate plans to transparently migrate existing scripts over to Lua. There are ideas about how this could be done safely without breaking existing content, but we want to make sure we have everything right before we start work on this.&lt;br /&gt;
&lt;br /&gt;
=== How does interoperation with LSL work? ===&lt;br /&gt;
&lt;br /&gt;
Everything that you expect from LSL should have some equivalent in Lua. There’s &amp;lt;code&amp;gt;key&amp;lt;/code&amp;gt;s, &amp;lt;code&amp;gt;rotation&amp;lt;/code&amp;gt;s and &amp;lt;code&amp;gt;vector&amp;lt;/code&amp;gt;s and something like &amp;lt;code&amp;gt;some_vec * some_rot&amp;lt;/code&amp;gt; in LSL is the exact same in Lua scripts.&lt;br /&gt;
&lt;br /&gt;
All LSL functions will be available in Lua, and something like &amp;lt;code&amp;gt;llSay(0, “hi!”)&amp;lt;/code&amp;gt; is just &amp;lt;code&amp;gt;ll.Say(0, “hi!”)&amp;lt;/code&amp;gt; now. What about &amp;lt;code&amp;gt;llSetPos(llGetPos() + &amp;lt;0, 0, 1&amp;gt;);&amp;lt;/code&amp;gt;? Just &amp;lt;code&amp;gt;ll.SetPos(ll.GetPos() + vector(0, 0, 1))&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
No special magic required here, and you can write whatever fancy wrappers you want around the existing LSL functions. We’ll be looking at how residents actually write these wrappers to inform the design of “official” higher-level Lua APIs, so we’re excited to see your ideas!&lt;br /&gt;
&lt;br /&gt;
=== Can I mix LSL and Lua in the same script? ===&lt;br /&gt;
&lt;br /&gt;
No, although most things should be a direct translation. At some point we may make a tool to help you do this, but there are no specific plans at the moment.&lt;br /&gt;
&lt;br /&gt;
If your object has multiple scripts, you can mix LSL and Lua within the same object, though.&lt;br /&gt;
&lt;br /&gt;
=== Can we use all of Lua? Is anything not there? ===&lt;br /&gt;
&lt;br /&gt;
Most everything will be there, except for two notable exceptions:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;getfenv()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;setfenv()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;_ENV&amp;lt;/code&amp;gt; are not accessible because they necessarily make the script harder to optimize. Usually people only use this for mocking in test code, and this can be accomplished by passing in a parameter with a table of function references rather than pretending you have different global variable values for certain functions.&lt;br /&gt;
* &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; is not supported since it has a huge impact on performance. For those not familiar, this is equivalent to eval() in other languages. Every single time you call &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; the compiler has to go over what you passed in, compile it, and then load the generated bytecode. The Lua compiler isn’t particularly fast, and doing this can cause your script to halt in a way that we can’t interrupt to give other scripts time to run. This is a big part of why &amp;lt;code&amp;gt;eval()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; are generally considered poor programming practice.&lt;br /&gt;
&lt;br /&gt;
The one real use-case we could think of for &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; is a debugger, and we’re looking at implementing a “real” debugger that lets you step through your code. Note that this won’t happen with the initial release, but watch this space!&lt;br /&gt;
&lt;br /&gt;
One thing that is planned, but will likely not be in the initial release is the coroutine library. This is one we’d really like to have in there, so you can do fancy things like have multiple “timers” in a script by just having coroutines that sleep every so often, but multiple running coroutines within the same script doesn’t fit well with the existing scripting architecture the Second Life server uses. Rest assured, this will be in there soon™ after Lua is released, but in the meantime you can still use something like the &amp;lt;code&amp;gt;Promise&amp;lt;/code&amp;gt; chaining that JavaScript uses to write asynchronous event handlers.&lt;br /&gt;
&lt;br /&gt;
We’ll have code demonstrating &amp;quot;proper&amp;quot; usage of Lua around the time that the public beta starts.&lt;br /&gt;
&lt;br /&gt;
=== Why Lua? I wanted C#! ===&lt;br /&gt;
&lt;br /&gt;
Great question!&lt;br /&gt;
&lt;br /&gt;
The server Lua scripting initiative actually started off as an attempt to get C# working on Second Life, basically as a revival of the work started in the 2000s. The initial thought was “this should be pretty easy, most of the work was already done getting everything onto Mono!” A lot of effort was put in over several months to:&lt;br /&gt;
&lt;br /&gt;
* Understand the Mono and .NET Core runtimes&lt;br /&gt;
* Observe how other Engines and Virtual Worlds have embedded C# and scripting in general&lt;br /&gt;
* Understand how recent advancements in scripting technologies (like WebAssembly) might make this integration easier&lt;br /&gt;
* Whether unique properties of how scripting / content creation works on Second Life would make this harder to implement&lt;br /&gt;
&lt;br /&gt;
Unfortunately, the reasons we were ultimately unable to do this were very technical, so there’s no side-stepping a whole lot of tech-speak here, but there were several key properties that made integrating C# in Second Life rather difficult to implement:&lt;br /&gt;
&lt;br /&gt;
==== Sandboxing with reflection is hard ====&lt;br /&gt;
&lt;br /&gt;
Previous proof-of-concept code for C# in Second Life relied on CoreCLR sandboxing in Mono. This approach has fallen out of favor in recent years, because similar approaches led to many, many security holes in Java’s browser plugin. This sandboxing method is not possible in .NET Core, and has been totally removed.&lt;br /&gt;
Since this sandboxing is no longer in .NET, we need to rely on the ability to look at code to determine if it looks “dangerous” before running it. Some people have done this, but it can only be done properly by disabling reflection, which is a core part of C# and .NET! Without reflection, you’re unable to use the fancy third-party Newtonsoft.JSON libraries or whatnot that you would have otherwise been able to use.&lt;br /&gt;
&lt;br /&gt;
Big portions of .NET’s standard library use reflection in a way that would be unsafe to expose to residents for scripting facilities, so you essentially have to maintain a list of functions and classes that are “safe” to use, and write alternatives to functions that are “unsafe” to call, but provide useful functionality. At this point, the scripting language that you’re exposing is a pretty bare-bones C# that needs to be scripted fundamentally differently from how you would write C# in Unity, for example.&lt;br /&gt;
&lt;br /&gt;
Allowing the use of modern C# features would require migration off of Mono and onto .NET core, which would be more involved than integrating Lua. Mono is in maintenance mode at the moment, and only supports the legacy .NET framework&lt;br /&gt;
Ensuring that we’re able to correctly limit each script’s memory usage in a low-impact way is a bit of an unsolved problem in .NET Core. Generally, memory allocation tracking is something that you only do for debugging, but we need to do it for all scripts! What happens if you declare a static global in a C# script? Is the memory usage counted against all scripts of that type? Do all scripts share the data in that static variable?&lt;br /&gt;
&lt;br /&gt;
What about if there’s a function in the standard library that keeps a static cache of data? How do we penalize (or not penalize) scripts for allocations that they didn’t even control? How do we make sure that data remains consistent across script unloads / reloads?&lt;br /&gt;
&lt;br /&gt;
These are problems that most people embedding C# don’t need to solve, and the solutions that do exist are not generally workable into Second Life’s scripting model without multiple months of work. Most other virtual worlds do script sandboxing on a per-parcel or per-region level and put everything into the same logical scripting sandbox, which Second Life does not do. People would be rightly annoyed if someone’s HUD or attachments caused their parcel to go over the memory limit and things stopped working.&lt;br /&gt;
&lt;br /&gt;
==== Making everything micro-threaded with extreme size constraints is hard ====&lt;br /&gt;
&lt;br /&gt;
We need to be able to temporarily pause scripts in the middle of running an event handler so we can ensure we fairly schedule code execution. Previously, we wrote a script to do some quite clever binary instrumentation of .NET assemblies to inject yield points. This works quite well for for LSL scripts, but there are issues with C# in particular.&lt;br /&gt;
&lt;br /&gt;
One is that it doesn’t handle the &amp;lt;code&amp;gt;try&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;finally&amp;lt;/code&amp;gt; construct in C#. While not impossible to handle, this would be a difficult undertaking. Something that would be much more difficult is allowing pausing execution inside a constructor or other such special constructs in C#. While the stack induction and bytecode manipulation done by our current system is very cool, it is quite complex to update, relies on outdated tools that don’t work with new .NET versions, and necessarily requires making the bytecode quite a bit larger than it would otherwise be by injecting hooks.&lt;br /&gt;
&lt;br /&gt;
But that’s not a huge deal, our .NET instrumentation library was written when &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt; was not a thing in C#, so we should be able to inject &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt; into user-provided source code instead to make it possible to transparently pause scripts right? Ehhhh, sort of. There are certain functions that cannot be made &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt; in C# without breaking things. Object constructors are one of those, we have no way to pause and resume inside those. The situation is similar inside &amp;lt;code&amp;gt;ToString()&amp;lt;/code&amp;gt; overloads. If we make a user-provided &amp;lt;code&amp;gt;ToString()&amp;lt;/code&amp;gt; overload async, it’ll just never get called in cases where ToString() would normally get called! And what about implementing &amp;lt;code&amp;gt;IEnumerable.GetIterator()&amp;lt;/code&amp;gt;? That might work… if you did a lot of work of replacing &amp;lt;code&amp;gt;IEnumerable&amp;lt;/code&amp;gt; with &amp;lt;code&amp;gt;IAsyncEnumerable&amp;lt;/code&amp;gt;., and &amp;lt;code&amp;gt;foreach&amp;lt;/code&amp;gt; with &amp;lt;code&amp;gt;await foreach&amp;lt;/code&amp;gt;... sometimes.&lt;br /&gt;
&lt;br /&gt;
Suffice to say, that if you go this route, you end up with something that’s sort-of C# but is missing a lot of things people expect of C#, and may not behave the same as the regular C# that it pretends to be.&lt;br /&gt;
&lt;br /&gt;
==== Saving script state ====&lt;br /&gt;
&lt;br /&gt;
But let’s not forget one of the most important cases where SL is unique: Scripts can move into your inventory, across region boundaries, etc, without having to care too much that it even happened. Even if your script runs forever in a single function, that’s totally fine! For example, this is a valid LSL script:&lt;br /&gt;
&lt;br /&gt;
  default {&lt;br /&gt;
    state_entry() {&lt;br /&gt;
      integer i;&lt;br /&gt;
      while(TRUE) {&lt;br /&gt;
        llSetText(&amp;quot;Run Number &amp;quot; + (string)(i++), &amp;lt;1,1,1&amp;gt;, 1);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
All it does is run forever in the same function, incrementing a number in the floating text above an object. It still works if you take the object into your inventory, it still works if you move the object to another region, as a scripter you don&#039;t really have to care except in limited circumstances.&lt;br /&gt;
&lt;br /&gt;
That&#039;s a pretty contrived example, but there are tons of scripts with a structure like this! Probably a more common example is a math-heavy function that just takes a while to run. Under the current model, you can interrupt what&#039;s going on by just picking up the object, then plop it back down later and let it continue on. There are a number of ways you can handle this without serializing the whole runtime state, like raising an exception in the middle of executing the function, but what are the chances that people will handle this correctly? How many command-line scripts have you used that do something really weird when you pressed &amp;lt;code&amp;gt;CTRL+C&amp;lt;/code&amp;gt;?&lt;br /&gt;
&lt;br /&gt;
To keep things simple for creators, we wanted to keep this nice property of LSL in whatever language we chose. This seemingly simple feature requires a lot of work because, think about it, how many programming languages let you seamlessly halt a program in the middle of it doing something, move it over to another computer, then resume whatever it was doing? Can you do that without doing a full-memory snapshot that would be gigabytes in size? That&#039;s not really a thing that people generally do, but it&#039;s required for us to not have sharp edges on our script creation experience!&lt;br /&gt;
&lt;br /&gt;
We have a version of this in our Mono scripting engine, which was all written in-house. It works very well for LSL, but it has trouble with more complex structures. As it turns out, taking all the state of a running program, serializing it, then transparently deserializing it is a difficult problem, even with data that &amp;lt;i&amp;gt;looks&amp;lt;/i&amp;gt; simple.&lt;br /&gt;
&lt;br /&gt;
Suppose we have something like &amp;lt;code&amp;gt;Dictionary&amp;lt;SomeObject, int&amp;gt;&amp;lt;/code&amp;gt; that we use as a counter for how many times we&#039;ve seen each &amp;lt;code&amp;gt;SomeObject&amp;lt;/code&amp;gt;. What do you do if the &amp;lt;code&amp;gt;GetHashCode()&amp;lt;/code&amp;gt; bucketing changes after the script deserializes and the memory increases? Can you prevent that from happening? Will live &amp;lt;code&amp;gt;IEnumerator&amp;lt;/code&amp;gt;s still work correctly? How can you be sure of this? .NET makes no guarantees about any of this behavior, and good luck digging through the .NET source code to figure out if any of this is even the case.&lt;br /&gt;
&lt;br /&gt;
What about types that aren&#039;t marked &amp;lt;code&amp;gt;[Serializable]&amp;lt;/code&amp;gt;, including types in the standard library? Can people just not use those? Do we have to write special custom serializers for them? Could they just randomly break when the script is deserialized and we accept that as a consequence?&lt;br /&gt;
&lt;br /&gt;
As you can see, there are a lot of hidden sharp edges involved in transparently moving scripts across servers, and the general answer provided when you ask about doing this is &amp;quot;That&#039;s very hard, make users write more intricate scripts that explicitly handle their own serialization and save their own state.&amp;quot; This is fine if most of your users are professional programmers, not so if you have a mix of professional programmers and regular users who just want an object that counts how many people have clicked on their head.&lt;br /&gt;
&lt;br /&gt;
==== Wrapping it up ====&lt;br /&gt;
&lt;br /&gt;
This is a very small subset of the issues we ran into while trying to design a satisfactory C#-based scripting system that fits Second Life&#039;s needs. Seriously, there&#039;s about 20 pages of notes.&lt;br /&gt;
&lt;br /&gt;
Meanwhile, Lua makes most of these things easy. Want to halt a script mid-execution? Okay, set an interrupt handler. Want to serialize your script while it&#039;s running and have it move to another server? Okay, there are libraries that have existed for decades that can do this. Fable&#039;s save file system was based on this. Quite a lot of scripting engine integration problems we have that would rise to the level of extremely difficult and relatively novel Computer Science research in .NET (and by extension, C#) are relatively simple to do with Lua.&lt;br /&gt;
&lt;br /&gt;
=== Okay, but seriously, why Lua specifically? Because it&#039;s popular for games? Because &amp;lt;other platform&amp;gt; uses it? Why not &amp;lt;X&amp;gt;? ===&lt;br /&gt;
&lt;br /&gt;
Actually, no. Neither other users of Lua, or a specific love of the language had anything to do with the decision. If you prefer a different programming language, there&#039;s a good chance we considered it. We actually looked at quite a lot of different languages and their associated engines, including JavaScript and WebAssembly, Lua was considered fairly late in the process. The ones below are just a small subset of the ones that we looked at:&lt;br /&gt;
&lt;br /&gt;
[[File:Lsl_vm_comparison.png|1000px]]&lt;br /&gt;
&lt;br /&gt;
As you can see, Luau was the only scripting runtime that fulfilled all of our requirements for a scripting engine within Second Life, and we arrived at it quite late in the decision process. We wanted something that we knew could deliver a high-quality scripting experience to creators, and we believe that Lua can best provide it.&lt;br /&gt;
&lt;br /&gt;
=== I genuinely hate Lua and refuse to use it ===&lt;br /&gt;
&lt;br /&gt;
A lot of people feel the same about LSL! Kidding aside, this is a totally defensible position, Lua can be a quirky language. Yes, it&#039;s strange that array indices start at 1. Yes, sometimes you can forget that you join strings with &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt;. It can take some getting used to.&lt;br /&gt;
&lt;br /&gt;
A big part of the reason why we&#039;re using Luau is due to the quality of the scripting engine itself, not specifically the language. A lot of issues people had with older versions of Lua have been addressed in Luau, and ones that haven&#039;t been for one reason or another can be easily dealt with since we have such a high degree of control over the scripting engine now. Luau is very easy to modify. If we find that there are real quality-of-life issues with Lua that people run into while writing scripts, we can just change how it works, assuming the benefits outweigh the cost of breaking interoperability with Lua scripts from elsewhere.&lt;br /&gt;
&lt;br /&gt;
But hey, maybe you really still hate the idea of using Lua. That&#039;s fine! The nice thing about having Lua instead of just LSL is there are a number of languages that can compile down to Lua code that could potentially work in Second Life! People have done work to get TypeScript and C# compiling to Lua, and it works pretty well for them, and there&#039;s also more exotic languages like MoonScript that target Lua specifically. See this GitHub repo for a listing: https://github.com/hengestone/lua-languages&lt;br /&gt;
&lt;br /&gt;
If you&#039;re so inclined, you can go as far as to take the Luau transpiler and hack it up to support &amp;lt;code&amp;gt;!=&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;//&amp;lt;/code&amp;gt; comments and really whatever you want. So long as it can take that source and output it to standard Lua, you&#039;re good.&lt;br /&gt;
&lt;br /&gt;
=== Are you going to be using Lua 5.1? Why not 5.4? ===&lt;br /&gt;
&lt;br /&gt;
We&#039;re using Luau as the base, which is &#039;&#039;compatible&#039;&#039; with Lua 5.1 but not necessarily Lua 5.1. It includes various features of PUC-Rio&#039;s Lua implementation up to and including 5.4. The Luau Compatibility reference is a good guide for which features are taken from which versions of PUC-Rio Lua: https://luau-lang.org/compatibility&lt;br /&gt;
&lt;br /&gt;
=== Are there going to be integer types in SL&#039;s Lua implementation? ===&lt;br /&gt;
&lt;br /&gt;
We have added 32-bit integers but they are strictly for compatibility with existing LSL APIs where integer type identity is important within a list (&amp;lt;code&amp;gt;llSetPrimitiveParams()&amp;lt;/code&amp;gt; and friends.) They don&#039;t have a literal form in Luau scripts and in general you should just use regular Lua numbers if you care about performance, since the Luau VM optimizes for their use, but technically you can use them. The Luau project discussions have some explanation as to why integers aren&#039;t a first-class user-facing type in Luau https://github.com/luau-lang/luau/discussions/242#discussioncomment-1707387.&lt;br /&gt;
&lt;br /&gt;
==== Are integer operations supported at all, then? (i.e. bitwise operators) ====&lt;br /&gt;
&lt;br /&gt;
Yes, through the &amp;lt;code&amp;gt;bit32&amp;lt;/code&amp;gt; library. In fact, &#039;&#039;more&#039;&#039; integer operations than are currently supported in LSL, like logical shift right and rotate operations. See https://luau-lang.org/library#bit32-library .&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218057</id>
		<title>Lua Alpha</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218057"/>
		<updated>2025-03-13T22:55:58Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{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.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;🚨 PLEASE NOTE Memory and performance characteristics, and API specifics may change! Scripts are currently being run in unoptimized form for development purposes.&#039;&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
= Second Life Lua (SLua) Alpha =&lt;br /&gt;
&lt;br /&gt;
[[File:Luau.png|720px|thumb|right|Luau logo]]&lt;br /&gt;
&lt;br /&gt;
We&#039;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.&lt;br /&gt;
&lt;br /&gt;
== What is SLua? ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Why Lua? ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== How to Get Started with SLua ==&lt;br /&gt;
&lt;br /&gt;
In order to play with SLua, you&#039;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].&lt;br /&gt;
&lt;br /&gt;
* Access the latest build of the SLua-enabled Second Life Viewer from [https://releasenotes.secondlife.com/viewer/7.1.12.13526902562.html here].&lt;br /&gt;
&lt;br /&gt;
Once you&#039;ve got the new viewer and have logged onto the beta grid, head over to these SLua-enabled regions:&lt;br /&gt;
&lt;br /&gt;
* [secondlife://Aditi/secondlife/SLua%20Yardang/241/235/27 SLua Yardang]&lt;br /&gt;
* [secondlife://Aditi/secondlife/SLua%20Tombolo/241/235/27 SLua Tombolo]&lt;br /&gt;
* [secondlife://Aditi/secondlife/SLua%20Mesa/241/235/27 SLua Mesa]&lt;br /&gt;
* [secondlife://Aditi/secondlife/SLua%20Tideland/241/235/27 SLua Tideland]&lt;br /&gt;
&lt;br /&gt;
When editing a script in the new Lua project viewer, you&#039;ll notice a new &#039;&#039;&#039;Compiler&#039;&#039;&#039; 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).&lt;br /&gt;
&lt;br /&gt;
[[File:Compiler_dropdown.png|Compiler selection dropdown]]&lt;br /&gt;
&lt;br /&gt;
Compiler drop-down options:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;LSL: Legacy (LSO2)&#039;&#039;&#039; - Scripts written in LSL, to be run on the old LSO2 VM&lt;br /&gt;
* &#039;&#039;&#039;LSL: Mono&#039;&#039;&#039;- Scripts written in LSL, to be run on the Mono VM&lt;br /&gt;
* &#039;&#039;&#039;Lua&#039;&#039;&#039; - Scripts written in Lua, to be run on the SLua VM&lt;br /&gt;
* &#039;&#039;&#039;LSL/Luau&#039;&#039;&#039;- Scripts written in LSL, to be run on the SLua VM&lt;br /&gt;
&lt;br /&gt;
=== Transitioning from LSL to SLua ===&lt;br /&gt;
* &#039;&#039;&#039;Function Namespacing:&#039;&#039;&#039;&lt;br /&gt;
** In SLua, Linden Lab functions have been moved under the &#039;&#039;&#039;ll&#039;&#039;&#039; namespace.&lt;br /&gt;
** For example:&lt;br /&gt;
*** &#039;&#039;llSay&#039;&#039; becomes &#039;&#039;ll.Say&#039;&#039;&lt;br /&gt;
*** &#039;&#039;llGetPos&#039;&#039; becomes &#039;&#039;ll.GetPos&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Lists&#039;&#039;&#039;&lt;br /&gt;
** Lua indexes begin from 1, unlike LSL where indexes begin from 0.&lt;br /&gt;
** Lua uses &amp;lt;code&amp;gt;{}&amp;lt;/code&amp;gt; for &#039;&#039;tables&#039;&#039;, unlike LSL where &amp;lt;code&amp;gt;[]&amp;lt;/code&amp;gt; is used for &#039;&#039;lists&#039;&#039;.&lt;br /&gt;
** When calling LL functions in SLua, lists often have type-strict requirements, unlike Luau in general.&lt;br /&gt;
*** For example, &amp;lt;code&amp;gt;ll.SetPrimitiveParams({PRIM_GLOW, 0, 1})&amp;lt;/code&amp;gt; will cause a type-error because &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; is a Luau &amp;lt;code&amp;gt;number&amp;lt;/code&amp;gt; type, instead of the LSL &amp;lt;code&amp;gt;integer&amp;lt;/code&amp;gt; type expected by [[llSetPrimitiveParams]]. For cases like this, there are special functions which provide the correct data types.&lt;br /&gt;
*** Correct: &amp;lt;code&amp;gt;ll.SetPrimitiveParams({PRIM_GLOW, integer(0), 1})&amp;lt;/code&amp;gt;&lt;br /&gt;
*** Similar functions exist for: &#039;&#039;&#039;integer&#039;&#039;&#039;, &#039;&#039;&#039;uuid&#039;&#039;&#039; (key), &#039;&#039;&#039;vector&#039;&#039;&#039;, &#039;&#039;&#039;quaternion&#039;&#039;&#039; (rotation)&lt;br /&gt;
&lt;br /&gt;
=== SLua Libraries ===&lt;br /&gt;
* &#039;&#039;&#039;Coroutines:&#039;&#039;&#039;&lt;br /&gt;
** SLua supports coroutines, allowing for cooperative multitasking within scripts.&lt;br /&gt;
** Key functions include:&lt;br /&gt;
*** &#039;&#039;coroutine.create&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.status&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.resume&#039;&#039;&lt;br /&gt;
** Refer to the [https://luau.org/library#coroutine-library coroutine library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Bitwise Operations:&#039;&#039;&#039;&lt;br /&gt;
** SLua includes a &#039;&#039;bit32&#039;&#039; library for bitwise operations, enabling more efficient data manipulation.&lt;br /&gt;
** Refer to the [https://luau.org/library#bit32-library bit32 library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Standard Library:&#039;&#039;&#039;&lt;br /&gt;
** SLua comes equipped with a standard library of functions designed to manipulate built-in data types.&lt;br /&gt;
** Explore the [https://luau.org/library Luau Standard Library] for a comprehensive list of available functions.&lt;br /&gt;
&lt;br /&gt;
== Feedback and Support ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== Example Scripts ==&lt;br /&gt;
&lt;br /&gt;
To help you get started, we&#039;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&#039;s desire!&lt;br /&gt;
&lt;br /&gt;
=== default_script.lua ===&lt;br /&gt;
This script is roughly equivalent to the default &amp;quot;new script&amp;quot; that gets created for LSL.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function state_entry()&lt;br /&gt;
    ll.Say(0, &amp;quot;Hello, Avatar!&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(total_number)&lt;br /&gt;
   ll.Say(0, &amp;quot;Touched.&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dialog.lua ===&lt;br /&gt;
This script demonstrates how one can interact with dialog menus.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Define the menu buttons and dialog message.&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local ToucherID = nil&lt;br /&gt;
local dialogChannel = nil&lt;br /&gt;
local listenHandle = nil&lt;br /&gt;
&lt;br /&gt;
-- This function is called when the script first starts.&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Get the object&#039;s key and compute a dialog channel number.&lt;br /&gt;
    local key = ll.GetKey()&lt;br /&gt;
    -- Extract the last 7 characters of the key and convert it from hex.&lt;br /&gt;
    dialogChannel = -1 - tonumber(string.sub(tostring(key), -7, -1), 16)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    ToucherID = ll.DetectedKey(0)&lt;br /&gt;
    -- If there is already a listen handle, then remove it&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
    end&lt;br /&gt;
    listenHandle = ll.Listen(dialogChannel, &amp;quot;&amp;quot;, ToucherID, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
    -- Set a 60-second timer for response.&lt;br /&gt;
    ll.SetTimerEvent(60.0)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when a dialog response is received.&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    if message == &amp;quot;-&amp;quot; then&lt;br /&gt;
        -- Redisplay the dialog if the &amp;quot;-&amp;quot; option is selected.&lt;br /&gt;
        ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Stop the timer, and stop the listening handler.&lt;br /&gt;
    ll.ListenRemove(listenHandle)&lt;br /&gt;
    ll.SetTimerEvent(0)&lt;br /&gt;
    -- Let the user know what they selected&lt;br /&gt;
    ll.Say(0, `You selected {message}`)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the timer expires.&lt;br /&gt;
function timer()&lt;br /&gt;
    -- Stop the timer and clean up the listener.&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.SetTimerEvent(0)&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
        ll.Whisper(0, &amp;quot;Sorry. You snooze; you lose.&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== user_input_coroutine.lua ===&lt;br /&gt;
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.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Wait for user input mid-function before doing something useful with it.&lt;br /&gt;
main = function(toucher)&lt;br /&gt;
    local handle = ll.Listen(0, &amp;quot;&amp;quot;, toucher, &amp;quot;&amp;quot;)&lt;br /&gt;
    local event = touch_start   -- save function for later&lt;br /&gt;
    touch_start = nil           -- disable touch_start&lt;br /&gt;
&lt;br /&gt;
    ll.RegionSayTo(toucher, 0, &amp;quot;Do you want pants or gloves?&amp;quot;)&lt;br /&gt;
    local clothing = coroutine.yield() -- pause the routine&#039;s execution here&lt;br /&gt;
    ll.RegionSayTo(toucher, 0, &amp;quot;For men or women?&amp;quot;)&lt;br /&gt;
    local gender = coroutine.yield()&lt;br /&gt;
    ll.RegionSayTo(toucher, 0, &amp;quot;Favorite color?&amp;quot;)&lt;br /&gt;
    local color = coroutine.yield()&lt;br /&gt;
    ll.RegionSayTo(toucher, 0, &amp;quot;Here&#039;s &amp;quot;..color..&amp;quot; &amp;quot;..clothing..&amp;quot; for &amp;quot;..gender)&lt;br /&gt;
&lt;br /&gt;
    ll.ListenRemove(handle)&lt;br /&gt;
    touch_start = event -- restore touch_start&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function touch_start(total_num)&lt;br /&gt;
    local toucher = ll.DetectedKey(0)&lt;br /&gt;
    routine = coroutine.create(main)    -- new coroutine&lt;br /&gt;
    coroutine.resume(routine, toucher)  -- run coroutine (with one argument)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- When the coroutine is suspended, incoming events can be handled&lt;br /&gt;
-- and we can resume() execution of the routine&lt;br /&gt;
-- and pass any number of arguments to be returned by yield()&lt;br /&gt;
function listen(channel, name, id, message)&lt;br /&gt;
    coroutine.resume(routine, message)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== multi_user_input_coroutine.lua ===&lt;br /&gt;
Following from the above example, how can we handle multiple users? This is where coroutines shine.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Key: avatar uuid; Value: coroutine thread&lt;br /&gt;
routines = {}&lt;br /&gt;
&lt;br /&gt;
main = function(toucher)&lt;br /&gt;
    local handle = ll.Listen(0, &amp;quot;&amp;quot;, toucher, &amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    ll.RegionSayTo(toucher, 0, &amp;quot;Do you want pants or gloves?&amp;quot;)&lt;br /&gt;
    local clothing = coroutine.yield()&lt;br /&gt;
    ll.RegionSayTo(toucher, 0, &amp;quot;For men or women?&amp;quot;)&lt;br /&gt;
    local gender = coroutine.yield()&lt;br /&gt;
    ll.RegionSayTo(toucher, 0, &amp;quot;Favorite color?&amp;quot;)&lt;br /&gt;
    local color = coroutine.yield()&lt;br /&gt;
    ll.RegionSayTo(toucher, 0, &amp;quot;Here&#039;s &amp;quot;..color..&amp;quot; &amp;quot;..clothing..&amp;quot; for &amp;quot;..gender)&lt;br /&gt;
&lt;br /&gt;
    ll.ListenRemove(handle)&lt;br /&gt;
    routines[toucher] = nil -- Remove from collection&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function touch_start(total_num)&lt;br /&gt;
    local toucher = ll.DetectedKey(0)&lt;br /&gt;
    local routine = routines[toucher]&lt;br /&gt;
    if not routine then -- New user needs new routine&lt;br /&gt;
        routine = coroutine.create(main)&lt;br /&gt;
        routines[toucher] = routine -- Add to collection&lt;br /&gt;
        coroutine.resume(routine, toucher)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function listen(channel, name, id, message)&lt;br /&gt;
    coroutine.resume(routines[id], message) -- Resume a specific coroutine&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== More Examples ===&lt;br /&gt;
&lt;br /&gt;
* Find more example scripts at [[Luau Example Scripts]]&lt;br /&gt;
&lt;br /&gt;
* [https://roblox.github.io/lua-style-guide/gotchas/ Lua Gotchas, Footguns and Other Hazards]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Try_Lua&amp;diff=1218055</id>
		<title>Try Lua</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Try_Lua&amp;diff=1218055"/>
		<updated>2025-03-13T20:23:02Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Created page with &amp;quot;&amp;#039;&amp;#039;&amp;#039;Try Out SLua (Lua in Second Life) on the Beta Grid!&amp;#039;&amp;#039;&amp;#039;  Experience the next evolution of scripting in Second Life with SLua. Follow these steps to dive in:  == 1. Create an...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Try Out SLua (Lua in Second Life) on the Beta Grid!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Experience the next evolution of scripting in Second Life with SLua. Follow these steps to dive in:&lt;br /&gt;
&lt;br /&gt;
== 1. Create an Account ==&lt;br /&gt;
# Go to [https://join.secondlife.com join.secondlife.com].&lt;br /&gt;
# Sign up for your free Second Life account.&lt;br /&gt;
&lt;br /&gt;
== 2. Install the Alpha Client ==&lt;br /&gt;
# Visit [https://second.life/alternate-viewers second.life/alternate-viewers].&lt;br /&gt;
# Download and install &#039;&#039;&#039;Second Life Project Lua Editor&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== 3. Connect to the Beta Grid ==&lt;br /&gt;
# Launch &#039;&#039;&#039;Project Lua Editor&#039;&#039;&#039;.&lt;br /&gt;
# Go to &#039;&#039;&#039;Me &amp;gt; Preferences &amp;gt; Advanced&#039;&#039;&#039; and enable &#039;&#039;&#039;Show Grid Selection at login&#039;&#039;&#039;.&lt;br /&gt;
# At the login screen, select &#039;&#039;&#039;Beta Test Grid (Aditi)&#039;&#039;&#039; from the drop-down menu.&lt;br /&gt;
# Click &#039;&#039;&#039;Log In&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== 4. Teleport to an SLua Region ==&lt;br /&gt;
# Click the &#039;&#039;&#039;Map&#039;&#039;&#039; button.&lt;br /&gt;
# Search for &#039;&#039;&#039;&amp;quot;SLua&amp;quot;&#039;&#039;&#039;.&lt;br /&gt;
# Select a region and click &#039;&#039;&#039;Teleport&#039;&#039;&#039; to enter a sandbox area.&lt;br /&gt;
&lt;br /&gt;
== 5. Start Scripting in SLua ==&lt;br /&gt;
# Enter Build mode (&#039;&#039;&#039;Build &amp;gt; Build&#039;&#039;&#039;).&lt;br /&gt;
# Rez a box, then open its &#039;&#039;&#039;Content&#039;&#039;&#039; tab.&lt;br /&gt;
# Click &#039;&#039;&#039;New Script&#039;&#039;&#039; and start coding in &#039;&#039;&#039;SLua&#039;&#039;&#039;!&lt;br /&gt;
&lt;br /&gt;
For more information about scripting with SLua, visit [[SLua Alpha]].&lt;br /&gt;
&lt;br /&gt;
=== Additional Resources ===&lt;br /&gt;
* [https://second.life/accessing-aditi Beta grid (Aditi) instructions]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218045</id>
		<title>Lua Alpha</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218045"/>
		<updated>2025-03-12T01:35:44Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{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.}}&lt;br /&gt;
&lt;br /&gt;
= Second Life Lua (SLua) Alpha =&lt;br /&gt;
&lt;br /&gt;
[[File:Luau.png|720px|thumb|right|Luau logo]]&lt;br /&gt;
&lt;br /&gt;
We&#039;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.&lt;br /&gt;
&lt;br /&gt;
== What is SLua? ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Why Lua? ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== How to Get Started with SLua ==&lt;br /&gt;
&lt;br /&gt;
In order to play with SLua, you&#039;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].&lt;br /&gt;
&lt;br /&gt;
* Access the latest build of the SLua-enabled Second Life Viewer from [https://releasenotes.secondlife.com/viewer/7.1.12.13526902562.html here].&lt;br /&gt;
&lt;br /&gt;
Once you&#039;ve got the new viewer and have logged onto the beta grid, head over to these SLua-enabled regions:&lt;br /&gt;
&lt;br /&gt;
* [secondlife://Aditi/secondlife/Luau%20Yardang/241/235/27 Luau Yardang]&lt;br /&gt;
* [secondlife://Aditi/secondlife/Luau%20Tombolo/241/235/27 Luau Tombolo]&lt;br /&gt;
* [secondlife://Aditi/secondlife/Luau%20Mesa/241/235/27 Luau Mesa]&lt;br /&gt;
* [secondlife://Aditi/secondlife/Luau%20Tideland/241/235/27 Luau Tideland]&lt;br /&gt;
&lt;br /&gt;
When editing a script in the new Lua project viewer, you&#039;ll notice a new &#039;&#039;&#039;Compiler&#039;&#039;&#039; 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).&lt;br /&gt;
&lt;br /&gt;
[[File:Compiler_dropdown.png|Compiler selection dropdown]]&lt;br /&gt;
&lt;br /&gt;
Compiler drop-down options:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;LSL: Legacy (LSO2)&#039;&#039;&#039; - Scripts written in LSL, to be run on the old LSO2 VM&lt;br /&gt;
* &#039;&#039;&#039;LSL: Mono&#039;&#039;&#039;- Scripts written in LSL, to be run on the Mono VM&lt;br /&gt;
* &#039;&#039;&#039;Lua&#039;&#039;&#039; - Scripts written in Lua, to be run on the SLua VM&lt;br /&gt;
* &#039;&#039;&#039;LSL/Luau&#039;&#039;&#039;- Scripts written in LSL, to be run on the SLua VM&lt;br /&gt;
&lt;br /&gt;
=== Transitioning from LSL to SLua ===&lt;br /&gt;
* &#039;&#039;&#039;Function Namespacing:&#039;&#039;&#039;&lt;br /&gt;
** In SLua, Linden Lab functions have been moved under the &#039;&#039;&#039;ll&#039;&#039;&#039; namespace.&lt;br /&gt;
** For example:&lt;br /&gt;
*** &#039;&#039;llSay&#039;&#039; becomes &#039;&#039;ll.Say&#039;&#039;&lt;br /&gt;
*** &#039;&#039;llGetPos&#039;&#039; becomes &#039;&#039;ll.GetPos&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Lists&#039;&#039;&#039;&lt;br /&gt;
** Lua indexes begin from 1, unlike LSL where indexes begin from 0.&lt;br /&gt;
** Lua uses &amp;lt;code&amp;gt;{}&amp;lt;/code&amp;gt; for &#039;&#039;tables&#039;&#039;, unlike LSL where &amp;lt;code&amp;gt;[]&amp;lt;/code&amp;gt; is used for &#039;&#039;lists&#039;&#039;.&lt;br /&gt;
** When calling LL functions in SLua, lists often have type-strict requirements, unlike Luau in general.&lt;br /&gt;
*** For example, &amp;lt;code&amp;gt;ll.SetPrimitiveParams({PRIM_GLOW, 0, 1})&amp;lt;/code&amp;gt; will cause a type-error because &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; is a Luau &amp;lt;code&amp;gt;number&amp;lt;/code&amp;gt; type, instead of the LSL &amp;lt;code&amp;gt;integer&amp;lt;/code&amp;gt; type expected by [[llSetPrimitiveParams]]. For cases like this, there are special functions which provide the correct data types.&lt;br /&gt;
*** Correct: &amp;lt;code&amp;gt;ll.SetPrimitiveParams({PRIM_GLOW, integer(0), 1})&amp;lt;/code&amp;gt;&lt;br /&gt;
*** Similar functions exist for: &#039;&#039;&#039;integer&#039;&#039;&#039;, &#039;&#039;&#039;uuid&#039;&#039;&#039; (key), &#039;&#039;&#039;vector&#039;&#039;&#039;, &#039;&#039;&#039;quaternion&#039;&#039;&#039; (rotation)&lt;br /&gt;
&lt;br /&gt;
=== SLua Libraries ===&lt;br /&gt;
* &#039;&#039;&#039;Coroutines:&#039;&#039;&#039;&lt;br /&gt;
** SLua supports coroutines, allowing for cooperative multitasking within scripts.&lt;br /&gt;
** Key functions include:&lt;br /&gt;
*** &#039;&#039;coroutine.create&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.status&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.resume&#039;&#039;&lt;br /&gt;
** Refer to the [https://luau.org/library#coroutine-library coroutine library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Bitwise Operations:&#039;&#039;&#039;&lt;br /&gt;
** SLua includes a &#039;&#039;bit32&#039;&#039; library for bitwise operations, enabling more efficient data manipulation.&lt;br /&gt;
** Refer to the [https://luau.org/library#bit32-library bit32 library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Standard Library:&#039;&#039;&#039;&lt;br /&gt;
** SLua comes equipped with a standard library of functions designed to manipulate built-in data types.&lt;br /&gt;
** Explore the [https://luau.org/library Luau Standard Library] for a comprehensive list of available functions.&lt;br /&gt;
&lt;br /&gt;
== Feedback and Support ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== Example Scripts ==&lt;br /&gt;
&lt;br /&gt;
To help you get started, we&#039;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&#039;s desire!&lt;br /&gt;
&lt;br /&gt;
=== default_script.lua ===&lt;br /&gt;
This script is roughly equivalent to the default &amp;quot;new script&amp;quot; that gets created for LSL.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function state_entry()&lt;br /&gt;
    ll.Say(0, &amp;quot;Hello, Avatar!&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(total_number)&lt;br /&gt;
   ll.Say(0, &amp;quot;Touched.&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dialog.lua ===&lt;br /&gt;
This script demonstrates how one can interact with dialog menus.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Define the menu buttons and dialog message.&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local ToucherID = nil&lt;br /&gt;
local dialogChannel = nil&lt;br /&gt;
local listenHandle = nil&lt;br /&gt;
&lt;br /&gt;
-- This function is called when the script first starts.&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Get the object&#039;s key and compute a dialog channel number.&lt;br /&gt;
    local key = ll.GetKey()&lt;br /&gt;
    -- Extract the last 7 characters of the key and convert it from hex.&lt;br /&gt;
    dialogChannel = -1 - tonumber(string.sub(tostring(key), -7, -1), 16)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    ToucherID = ll.DetectedKey(0)&lt;br /&gt;
    -- If there is already a listen handle, then remove it&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
    end&lt;br /&gt;
    listenHandle = ll.Listen(dialogChannel, &amp;quot;&amp;quot;, ToucherID, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
    -- Set a 60-second timer for response.&lt;br /&gt;
    ll.SetTimerEvent(60.0)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when a dialog response is received.&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    if message == &amp;quot;-&amp;quot; then&lt;br /&gt;
        -- Redisplay the dialog if the &amp;quot;-&amp;quot; option is selected.&lt;br /&gt;
        ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Stop the timer, and stop the listening handler.&lt;br /&gt;
    ll.ListenRemove(listenHandle)&lt;br /&gt;
    ll.SetTimerEvent(0)&lt;br /&gt;
    -- Let the user know what they selected&lt;br /&gt;
    ll.Say(0, `You selected {message}`)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the timer expires.&lt;br /&gt;
function timer()&lt;br /&gt;
    -- Stop the timer and clean up the listener.&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.SetTimerEvent(0)&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
        ll.Whisper(0, &amp;quot;Sorry. You snooze; you lose.&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== user_input_coroutine.lua ===&lt;br /&gt;
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.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Wait for user-input before doing something useful with it.&lt;br /&gt;
main = function()&lt;br /&gt;
    ll.Listen(0, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Do you want pants or gloves?&amp;quot;)&lt;br /&gt;
    local clothing = coroutine.yield() -- pause the routine&#039;s execution here&lt;br /&gt;
    ll.OwnerSay(&amp;quot;For men or women?&amp;quot;)&lt;br /&gt;
    local gender = coroutine.yield()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Favorite color?&amp;quot;)&lt;br /&gt;
    local color = coroutine.yield()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Here&#039;s &amp;quot;..color..&amp;quot; &amp;quot;..clothing..&amp;quot; for &amp;quot;..gender)&lt;br /&gt;
    finished = true&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function touch_start(total_num)&lt;br /&gt;
    if finished ~= false then -- unset or true&lt;br /&gt;
        finished = false&lt;br /&gt;
        routine = coroutine.create(main) -- new coroutine&lt;br /&gt;
        coroutine.resume(routine)        -- run coroutine&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- When the coroutine is suspended,&lt;br /&gt;
-- we can resume its execution by calling the coroutine again&lt;br /&gt;
-- and pass any number of arguments to be returned by yield()&lt;br /&gt;
function listen(channel, name, id, message)&lt;br /&gt;
    if finished == false then&lt;br /&gt;
        coroutine.resume(routine, message)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dialog_coroutine.lua ===&lt;br /&gt;
This script demonstrates how one could use coroutines to handle dialog responses, with multi-user support.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Minimal EventLoop&lt;br /&gt;
-------------------------&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; eventName it’s waiting for&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status(coro) ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Internal helper: resumes a coroutine&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local ok, eventAwaited = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    if not ok then&lt;br /&gt;
        ll.OwnerSay(`Coroutine error: {eventAwaited}`)&lt;br /&gt;
        self._coros[coro] = nil&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- If still alive, &#039;eventAwaited&#039; is the next event it wants&lt;br /&gt;
    self._coros[coro] = eventAwaited&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:handle_event(eventName, ...)&lt;br /&gt;
    ll.OwnerSay(`Handling event {eventName}`)&lt;br /&gt;
    local snapshot = table.clone(self._coros)&lt;br /&gt;
    for coro, waitingFor in pairs(snapshot) do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        elseif waitingFor == eventName then&lt;br /&gt;
            ll.OwnerSay(`Dispatching event {eventName} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Coroutines use this to yield until an event&lt;br /&gt;
local function await_event(name)&lt;br /&gt;
    assert(EventLoop.running, &amp;quot;await_event called outside a coroutine!&amp;quot;)&lt;br /&gt;
    return coroutine.yield(name)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Script Logic&lt;br /&gt;
-------------------------&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- Use the chat listener to feed the event-loop&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    -- We handle all &#039;listen&#039; events via the event-loop&lt;br /&gt;
    EventLoop:handle_event(`listen_{channel}`, channel, name, sender_id, message)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the script starts&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Seed math.random so each new script run doesn’t repeat the same channels&lt;br /&gt;
    math.randomseed(ll.GetUnixTime())&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Script started with random channels for each user.&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- A coroutine function for a single user&#039;s dialog flow&lt;br /&gt;
local function handle_dialog_for_user(userId)&lt;br /&gt;
    -- Use a random channel&lt;br /&gt;
    local channel = math.random(0x1, 0xFFFF)&lt;br /&gt;
    -- Create a listener for that channel&lt;br /&gt;
    local listenHandle = ll.Listen(channel, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        -- Show the user a dialog&lt;br /&gt;
        ll.Dialog(userId, dialogInfo, buttons, channel)&lt;br /&gt;
&lt;br /&gt;
        -- Wait for the next &#039;listen&#039; event (channel, name, sender_id, message)&lt;br /&gt;
        local c, n, sid, msg = await_event(`listen_{channel}`)&lt;br /&gt;
&lt;br /&gt;
        -- If this &amp;quot;listen&amp;quot; event isn&#039;t for our channel/user, ignore it&lt;br /&gt;
        if c == channel and sid == userId then&lt;br /&gt;
            -- If user pressed &amp;quot;-&amp;quot;, re-display the menu, else they picked a final color&lt;br /&gt;
            if msg ~= &amp;quot;-&amp;quot; then&lt;br /&gt;
                ll.Say(0, `{n} selected {msg}`)&lt;br /&gt;
                -- Now that they&#039;ve chosen something else, remove the listener and finish&lt;br /&gt;
                ll.ListenRemove(listenHandle)&lt;br /&gt;
                return&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    for i=0, num_detected-1 do&lt;br /&gt;
        local toucherId = ll.DetectedKey(i)&lt;br /&gt;
        -- Create a separate coroutine for each person who touches&lt;br /&gt;
        EventLoop:create_task(function()&lt;br /&gt;
            handle_dialog_for_user(toucherId)&lt;br /&gt;
        end)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Run state_entry on load:&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== More Examples ===&lt;br /&gt;
&lt;br /&gt;
* Find more example scripts at [[Luau Example Scripts]]&lt;br /&gt;
&lt;br /&gt;
* [https://roblox.github.io/lua-style-guide/gotchas/ Lua Gotchas, Footguns and Other Hazards]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Luau_Alpha&amp;diff=1218044</id>
		<title>Luau Alpha</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Luau_Alpha&amp;diff=1218044"/>
		<updated>2025-03-12T01:32:11Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Signal Linden moved page Luau Alpha to SLua Alpha&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[SLua Alpha]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218043</id>
		<title>Lua Alpha</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218043"/>
		<updated>2025-03-12T01:32:09Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Signal Linden moved page Luau Alpha to SLua Alpha&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{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.}}&lt;br /&gt;
&lt;br /&gt;
= Second Life Luau Alpha =&lt;br /&gt;
&lt;br /&gt;
[[File:Luau.png|720px|thumb|right|Luau logo]]&lt;br /&gt;
&lt;br /&gt;
We&#039;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.&lt;br /&gt;
&lt;br /&gt;
== What is Luau? ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Why Luau? ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== How to Get Started with Luau in Second Life ==&lt;br /&gt;
&lt;br /&gt;
In order to play with Luau, you&#039;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].&lt;br /&gt;
&lt;br /&gt;
* Access the latest build of the Luau-enabled Second Life Viewer from [https://releasenotes.secondlife.com/viewer/7.1.12.13526902562.html here].&lt;br /&gt;
&lt;br /&gt;
Once you&#039;ve got the new viewer and have logged onto the beta grid, head over to these Luau-enabled regions:&lt;br /&gt;
&lt;br /&gt;
* [secondlife://Aditi/secondlife/Luau%20Yardang/241/235/27 Luau Yardang]&lt;br /&gt;
* [secondlife://Aditi/secondlife/Luau%20Tombolo/241/235/27 Luau Tombolo]&lt;br /&gt;
* [secondlife://Aditi/secondlife/Luau%20Mesa/241/235/27 Luau Mesa]&lt;br /&gt;
* [secondlife://Aditi/secondlife/Luau%20Tideland/241/235/27 Luau Tideland]&lt;br /&gt;
&lt;br /&gt;
When editing a script in the new Lua project viewer, you&#039;ll notice a new &#039;&#039;&#039;Compiler&#039;&#039;&#039; 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).&lt;br /&gt;
&lt;br /&gt;
[[File:Compiler_dropdown.png|Compiler selection dropdown]]&lt;br /&gt;
&lt;br /&gt;
Compiler drop-down options:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;LSL: Legacy (LSO2)&#039;&#039;&#039; - Scripts written in LSL, to be run on the old LSO2 VM&lt;br /&gt;
* &#039;&#039;&#039;LSL: Mono&#039;&#039;&#039;- Scripts written in LSL, to be run on the Mono VM&lt;br /&gt;
* &#039;&#039;&#039;Lua&#039;&#039;&#039; - Scripts written in Lua, to be run on the Luau VM&lt;br /&gt;
* &#039;&#039;&#039;LSL/Luau&#039;&#039;&#039;- Scripts written in LSL, to be run on the Luau VM&lt;br /&gt;
&lt;br /&gt;
=== Transitioning from LSL to Luau ===&lt;br /&gt;
* &#039;&#039;&#039;Function Namespacing:&#039;&#039;&#039;&lt;br /&gt;
** In Luau, Linden Lab functions have been moved under the &#039;&#039;&#039;ll&#039;&#039;&#039; namespace.&lt;br /&gt;
** For example:&lt;br /&gt;
*** &#039;&#039;llSay&#039;&#039; becomes &#039;&#039;ll.Say&#039;&#039;&lt;br /&gt;
*** &#039;&#039;llGetPos&#039;&#039; becomes &#039;&#039;ll.GetPos&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Lists&#039;&#039;&#039;&lt;br /&gt;
** Luau indexes begin from 1, unlike LSL where indexes begin from 0.&lt;br /&gt;
** Luau uses &amp;lt;code&amp;gt;{}&amp;lt;/code&amp;gt; for &#039;&#039;tables&#039;&#039;, unlike LSL where &amp;lt;code&amp;gt;[]&amp;lt;/code&amp;gt; is used for &#039;&#039;lists&#039;&#039;.&lt;br /&gt;
** When calling LL functions in Luau, lists often have type-strict requirements, unlike Luau in general.&lt;br /&gt;
*** For example, &amp;lt;code&amp;gt;ll.SetPrimitiveParams({PRIM_GLOW, 0, 1})&amp;lt;/code&amp;gt; will cause a type-error because &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; is a Luau &amp;lt;code&amp;gt;number&amp;lt;/code&amp;gt; type, instead of the LSL &amp;lt;code&amp;gt;integer&amp;lt;/code&amp;gt; type expected by [[llSetPrimitiveParams]]. For cases like this, there are special functions which provide the correct data types.&lt;br /&gt;
*** Correct: &amp;lt;code&amp;gt;ll.SetPrimitiveParams({PRIM_GLOW, integer(0), 1})&amp;lt;/code&amp;gt;&lt;br /&gt;
*** Similar functions exist for: &#039;&#039;&#039;integer&#039;&#039;&#039;, &#039;&#039;&#039;uuid&#039;&#039;&#039; (key), &#039;&#039;&#039;vector&#039;&#039;&#039;, &#039;&#039;&#039;quaternion&#039;&#039;&#039; (rotation)&lt;br /&gt;
&lt;br /&gt;
=== Luau Libraries ===&lt;br /&gt;
* &#039;&#039;&#039;Coroutines:&#039;&#039;&#039;&lt;br /&gt;
** Luau supports coroutines, allowing for cooperative multitasking within scripts.&lt;br /&gt;
** Key functions include:&lt;br /&gt;
*** &#039;&#039;coroutine.create&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.status&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.resume&#039;&#039;&lt;br /&gt;
** Refer to the [https://luau.org/library#coroutine-library coroutine library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Bitwise Operations:&#039;&#039;&#039;&lt;br /&gt;
** Luau includes a &#039;&#039;bit32&#039;&#039; library for bitwise operations, enabling more efficient data manipulation.&lt;br /&gt;
** Refer to the [https://luau.org/library#bit32-library bit32 library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Standard Library:&#039;&#039;&#039;&lt;br /&gt;
** Luau comes equipped with a standard library of functions designed to manipulate built-in data types.&lt;br /&gt;
** Explore the [https://luau.org/library Luau Standard Library] for a comprehensive list of available functions.&lt;br /&gt;
&lt;br /&gt;
== Feedback and Support ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== Example Scripts ==&lt;br /&gt;
&lt;br /&gt;
To help you get started, we&#039;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&#039;s desire!&lt;br /&gt;
&lt;br /&gt;
=== default_script.lua ===&lt;br /&gt;
This script is roughly equivalent to the default &amp;quot;new script&amp;quot; that gets created for LSL.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function state_entry()&lt;br /&gt;
    ll.Say(0, &amp;quot;Hello, Avatar!&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(total_number)&lt;br /&gt;
   ll.Say(0, &amp;quot;Touched.&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dialog.lua ===&lt;br /&gt;
This script demonstrates how one can interact with dialog menus.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Define the menu buttons and dialog message.&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local ToucherID = nil&lt;br /&gt;
local dialogChannel = nil&lt;br /&gt;
local listenHandle = nil&lt;br /&gt;
&lt;br /&gt;
-- This function is called when the script first starts.&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Get the object&#039;s key and compute a dialog channel number.&lt;br /&gt;
    local key = ll.GetKey()&lt;br /&gt;
    -- Extract the last 7 characters of the key and convert it from hex.&lt;br /&gt;
    dialogChannel = -1 - tonumber(string.sub(tostring(key), -7, -1), 16)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    ToucherID = ll.DetectedKey(0)&lt;br /&gt;
    -- If there is already a listen handle, then remove it&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
    end&lt;br /&gt;
    listenHandle = ll.Listen(dialogChannel, &amp;quot;&amp;quot;, ToucherID, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
    -- Set a 60-second timer for response.&lt;br /&gt;
    ll.SetTimerEvent(60.0)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when a dialog response is received.&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    if message == &amp;quot;-&amp;quot; then&lt;br /&gt;
        -- Redisplay the dialog if the &amp;quot;-&amp;quot; option is selected.&lt;br /&gt;
        ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Stop the timer, and stop the listening handler.&lt;br /&gt;
    ll.ListenRemove(listenHandle)&lt;br /&gt;
    ll.SetTimerEvent(0)&lt;br /&gt;
    -- Let the user know what they selected&lt;br /&gt;
    ll.Say(0, `You selected {message}`)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the timer expires.&lt;br /&gt;
function timer()&lt;br /&gt;
    -- Stop the timer and clean up the listener.&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.SetTimerEvent(0)&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
        ll.Whisper(0, &amp;quot;Sorry. You snooze; you lose.&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== user_input_coroutine.lua ===&lt;br /&gt;
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.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Wait for user-input before doing something useful with it.&lt;br /&gt;
main = function()&lt;br /&gt;
    ll.Listen(0, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Do you want pants or gloves?&amp;quot;)&lt;br /&gt;
    local clothing = coroutine.yield() -- pause the routine&#039;s execution here&lt;br /&gt;
    ll.OwnerSay(&amp;quot;For men or women?&amp;quot;)&lt;br /&gt;
    local gender = coroutine.yield()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Favorite color?&amp;quot;)&lt;br /&gt;
    local color = coroutine.yield()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Here&#039;s &amp;quot;..color..&amp;quot; &amp;quot;..clothing..&amp;quot; for &amp;quot;..gender)&lt;br /&gt;
    finished = true&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function touch_start(total_num)&lt;br /&gt;
    if finished ~= false then -- unset or true&lt;br /&gt;
        finished = false&lt;br /&gt;
        routine = coroutine.create(main) -- new coroutine&lt;br /&gt;
        coroutine.resume(routine)        -- run coroutine&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- When the coroutine is suspended,&lt;br /&gt;
-- we can resume its execution by calling the coroutine again&lt;br /&gt;
-- and pass any number of arguments to be returned by yield()&lt;br /&gt;
function listen(channel, name, id, message)&lt;br /&gt;
    if finished == false then&lt;br /&gt;
        coroutine.resume(routine, message)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dialog_coroutine.lua ===&lt;br /&gt;
This script demonstrates how one could use coroutines to handle dialog responses, with multi-user support.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Minimal EventLoop&lt;br /&gt;
-------------------------&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; eventName it’s waiting for&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status(coro) ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Internal helper: resumes a coroutine&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local ok, eventAwaited = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    if not ok then&lt;br /&gt;
        ll.OwnerSay(`Coroutine error: {eventAwaited}`)&lt;br /&gt;
        self._coros[coro] = nil&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- If still alive, &#039;eventAwaited&#039; is the next event it wants&lt;br /&gt;
    self._coros[coro] = eventAwaited&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:handle_event(eventName, ...)&lt;br /&gt;
    ll.OwnerSay(`Handling event {eventName}`)&lt;br /&gt;
    local snapshot = table.clone(self._coros)&lt;br /&gt;
    for coro, waitingFor in pairs(snapshot) do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        elseif waitingFor == eventName then&lt;br /&gt;
            ll.OwnerSay(`Dispatching event {eventName} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Coroutines use this to yield until an event&lt;br /&gt;
local function await_event(name)&lt;br /&gt;
    assert(EventLoop.running, &amp;quot;await_event called outside a coroutine!&amp;quot;)&lt;br /&gt;
    return coroutine.yield(name)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Script Logic&lt;br /&gt;
-------------------------&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- Use the chat listener to feed the event-loop&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    -- We handle all &#039;listen&#039; events via the event-loop&lt;br /&gt;
    EventLoop:handle_event(`listen_{channel}`, channel, name, sender_id, message)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the script starts&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Seed math.random so each new script run doesn’t repeat the same channels&lt;br /&gt;
    math.randomseed(ll.GetUnixTime())&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Script started with random channels for each user.&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- A coroutine function for a single user&#039;s dialog flow&lt;br /&gt;
local function handle_dialog_for_user(userId)&lt;br /&gt;
    -- Use a random channel&lt;br /&gt;
    local channel = math.random(0x1, 0xFFFF)&lt;br /&gt;
    -- Create a listener for that channel&lt;br /&gt;
    local listenHandle = ll.Listen(channel, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        -- Show the user a dialog&lt;br /&gt;
        ll.Dialog(userId, dialogInfo, buttons, channel)&lt;br /&gt;
&lt;br /&gt;
        -- Wait for the next &#039;listen&#039; event (channel, name, sender_id, message)&lt;br /&gt;
        local c, n, sid, msg = await_event(`listen_{channel}`)&lt;br /&gt;
&lt;br /&gt;
        -- If this &amp;quot;listen&amp;quot; event isn&#039;t for our channel/user, ignore it&lt;br /&gt;
        if c == channel and sid == userId then&lt;br /&gt;
            -- If user pressed &amp;quot;-&amp;quot;, re-display the menu, else they picked a final color&lt;br /&gt;
            if msg ~= &amp;quot;-&amp;quot; then&lt;br /&gt;
                ll.Say(0, `{n} selected {msg}`)&lt;br /&gt;
                -- Now that they&#039;ve chosen something else, remove the listener and finish&lt;br /&gt;
                ll.ListenRemove(listenHandle)&lt;br /&gt;
                return&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    for i=0, num_detected-1 do&lt;br /&gt;
        local toucherId = ll.DetectedKey(i)&lt;br /&gt;
        -- Create a separate coroutine for each person who touches&lt;br /&gt;
        EventLoop:create_task(function()&lt;br /&gt;
            handle_dialog_for_user(toucherId)&lt;br /&gt;
        end)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Run state_entry on load:&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== More Examples ===&lt;br /&gt;
&lt;br /&gt;
* Find more example scripts at [[Luau Example Scripts]]&lt;br /&gt;
&lt;br /&gt;
* [https://roblox.github.io/lua-style-guide/gotchas/ Lua Gotchas, Footguns and Other Hazards]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218020</id>
		<title>Lua Alpha</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218020"/>
		<updated>2025-03-05T01:09:09Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{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.}}&lt;br /&gt;
&lt;br /&gt;
= Second Life Luau Alpha =&lt;br /&gt;
&lt;br /&gt;
[[File:Luau.png|720px|thumb|right|Luau logo]]&lt;br /&gt;
&lt;br /&gt;
We&#039;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.&lt;br /&gt;
&lt;br /&gt;
== What is Luau? ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Why Luau? ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== How to Get Started with Luau in Second Life ==&lt;br /&gt;
&lt;br /&gt;
In order to play with Luau, you&#039;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].&lt;br /&gt;
&lt;br /&gt;
* Access the latest build of the Luau-enabled Second Life Viewer from [https://releasenotes.secondlife.com/viewer/7.1.12.13526902562.html here].&lt;br /&gt;
&lt;br /&gt;
Once you&#039;ve got the new viewer and have logged onto the beta grid, head over to these Luau-enabled regions:&lt;br /&gt;
&lt;br /&gt;
* [secondlife://Aditi/secondlife/Luau%20Yardang/241/235/27 Luau Yardang]&lt;br /&gt;
* [secondlife://Aditi/secondlife/Luau%20Tombolo/241/235/27 Luau Tombolo]&lt;br /&gt;
* [secondlife://Aditi/secondlife/Luau%20Mesa/241/235/27 Luau Mesa]&lt;br /&gt;
* [secondlife://Aditi/secondlife/Luau%20Tideland/241/235/27 Luau Tideland]&lt;br /&gt;
&lt;br /&gt;
When editing a script in the new Lua project viewer, you&#039;ll notice a new &#039;&#039;&#039;Compiler&#039;&#039;&#039; 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).&lt;br /&gt;
&lt;br /&gt;
[[File:Compiler_dropdown.png|Compiler selection dropdown]]&lt;br /&gt;
&lt;br /&gt;
Compiler drop-down options:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;LSL: Legacy (LSO2)&#039;&#039;&#039; - Scripts written in LSL, to be run on the old LSO2 VM&lt;br /&gt;
* &#039;&#039;&#039;LSL: Mono&#039;&#039;&#039;- Scripts written in LSL, to be run on the Mono VM&lt;br /&gt;
* &#039;&#039;&#039;Lua&#039;&#039;&#039; - Scripts written in Lua, to be run on the Luau VM&lt;br /&gt;
* &#039;&#039;&#039;LSL/Luau&#039;&#039;&#039;- Scripts written in LSL, to be run on the Luau VM&lt;br /&gt;
&lt;br /&gt;
=== Transitioning from LSL to Luau ===&lt;br /&gt;
* &#039;&#039;&#039;Function Namespacing:&#039;&#039;&#039;&lt;br /&gt;
** In Luau, Linden Lab functions have been moved under the &#039;&#039;&#039;ll&#039;&#039;&#039; namespace.&lt;br /&gt;
** For example:&lt;br /&gt;
*** &#039;&#039;llSay&#039;&#039; becomes &#039;&#039;ll.Say&#039;&#039;&lt;br /&gt;
*** &#039;&#039;llGetPos&#039;&#039; becomes &#039;&#039;ll.GetPos&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Constants:&#039;&#039;&#039;&lt;br /&gt;
** Constants like &#039;&#039;PERMISSION_TAKE_CONTROLS&#039;&#039; and &#039;&#039;PRIM_ROT_LOCAL&#039;&#039; are not predefined in Luau.&lt;br /&gt;
** You must specify these constants if you wish to reference them in your scripts.&lt;br /&gt;
&lt;br /&gt;
=== Luau Libraries ===&lt;br /&gt;
* &#039;&#039;&#039;Coroutines:&#039;&#039;&#039;&lt;br /&gt;
** Luau supports coroutines, allowing for cooperative multitasking within scripts.&lt;br /&gt;
** Key functions include:&lt;br /&gt;
*** &#039;&#039;coroutine.create&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.status&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.resume&#039;&#039;&lt;br /&gt;
** Refer to the [https://luau.org/library#coroutine-library coroutine library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Bitwise Operations:&#039;&#039;&#039;&lt;br /&gt;
** Luau includes a &#039;&#039;bit32&#039;&#039; library for bitwise operations, enabling more efficient data manipulation.&lt;br /&gt;
** Refer to the [https://luau.org/library#bit32-library bit32 library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Standard Library:&#039;&#039;&#039;&lt;br /&gt;
** Luau comes equipped with a standard library of functions designed to manipulate built-in data types.&lt;br /&gt;
** Explore the [https://luau.org/library Luau Standard Library] for a comprehensive list of available functions.&lt;br /&gt;
&lt;br /&gt;
== Feedback and Support ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== Example Scripts ==&lt;br /&gt;
&lt;br /&gt;
To help you get started, we&#039;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&#039;s desire!&lt;br /&gt;
&lt;br /&gt;
=== default_script.lua ===&lt;br /&gt;
This script is roughly equivalent to the &amp;quot;new script&amp;quot; that gets created.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function state_entry()&lt;br /&gt;
    ll.Say(0, &amp;quot;Hello, Avatar!&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(total_number)&lt;br /&gt;
   ll.Say(0, &amp;quot;Touched.&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dialog.lua ===&lt;br /&gt;
This script demonstrates how one can interact with dialog menus.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Define the menu buttons and dialog message.&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local ToucherID = nil&lt;br /&gt;
local dialogChannel = nil&lt;br /&gt;
local listenHandle = nil&lt;br /&gt;
&lt;br /&gt;
-- This function is called when the script first starts.&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Get the object&#039;s key and compute a dialog channel number.&lt;br /&gt;
    local key = ll.GetKey()&lt;br /&gt;
    -- Extract the last 7 characters of the key and convert it from hex.&lt;br /&gt;
    dialogChannel = -1 - tonumber(string.sub(tostring(key), -7, -1), 16)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    ToucherID = ll.DetectedKey(0)&lt;br /&gt;
    -- If there is already a listen handle, then remove it&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
    end&lt;br /&gt;
    listenHandle = ll.Listen(dialogChannel, &amp;quot;&amp;quot;, ToucherID, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
    -- Set a 60-second timer for response.&lt;br /&gt;
    ll.SetTimerEvent(60.0)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when a dialog response is received.&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    if message == &amp;quot;-&amp;quot; then&lt;br /&gt;
        -- Redisplay the dialog if the &amp;quot;-&amp;quot; option is selected.&lt;br /&gt;
        ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Stop the timer, and stop the listening handler.&lt;br /&gt;
    ll.ListenRemove(listenHandle)&lt;br /&gt;
    ll.SetTimerEvent(0)&lt;br /&gt;
    -- Let the user know what they selected&lt;br /&gt;
    ll.Say(0, `You selected {message}`)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the timer expires.&lt;br /&gt;
function timer()&lt;br /&gt;
    -- Stop the timer and clean up the listener.&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.SetTimerEvent(0)&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
        ll.Whisper(0, &amp;quot;Sorry. You snooze; you lose.&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dialog_coroutine.lua ===&lt;br /&gt;
This script demonstrates how one could use coroutines to handle dialog responses, with multi-user support.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Minimal EventLoop&lt;br /&gt;
-------------------------&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; eventName it’s waiting for&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status(coro) ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Internal helper: resumes a coroutine&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local ok, eventAwaited = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    if not ok then&lt;br /&gt;
        ll.OwnerSay(`Coroutine error: {eventAwaited}`)&lt;br /&gt;
        self._coros[coro] = nil&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- If still alive, &#039;eventAwaited&#039; is the next event it wants&lt;br /&gt;
    self._coros[coro] = eventAwaited&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:handle_event(eventName, ...)&lt;br /&gt;
    ll.OwnerSay(`Handling event {eventName}`)&lt;br /&gt;
    local snapshot = table.clone(self._coros)&lt;br /&gt;
    for coro, waitingFor in pairs(snapshot) do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        elseif waitingFor == eventName then&lt;br /&gt;
            ll.OwnerSay(`Dispatching event {eventName} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Coroutines use this to yield until an event&lt;br /&gt;
local function await_event(name)&lt;br /&gt;
    assert(EventLoop.running, &amp;quot;await_event called outside a coroutine!&amp;quot;)&lt;br /&gt;
    return coroutine.yield(name)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Script Logic&lt;br /&gt;
-------------------------&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- Use the chat listener to feed the event-loop&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    -- We handle all &#039;listen&#039; events via the event-loop&lt;br /&gt;
    EventLoop:handle_event(`listen_{channel}`, channel, name, sender_id, message)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the script starts&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Seed math.random so each new script run doesn’t repeat the same channels&lt;br /&gt;
    math.randomseed(ll.GetUnixTime())&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Script started with random channels for each user.&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- A coroutine function for a single user&#039;s dialog flow&lt;br /&gt;
local function handle_dialog_for_user(userId)&lt;br /&gt;
    -- Use a random channel&lt;br /&gt;
    local channel = math.random(0x1, 0xFFFF)&lt;br /&gt;
    -- Create a listener for that channel&lt;br /&gt;
    local listenHandle = ll.Listen(channel, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        -- Show the user a dialog&lt;br /&gt;
        ll.Dialog(userId, dialogInfo, buttons, channel)&lt;br /&gt;
&lt;br /&gt;
        -- Wait for the next &#039;listen&#039; event (channel, name, sender_id, message)&lt;br /&gt;
        local c, n, sid, msg = await_event(`listen_{channel}`)&lt;br /&gt;
&lt;br /&gt;
        -- If this &amp;quot;listen&amp;quot; event isn&#039;t for our channel/user, ignore it&lt;br /&gt;
        if c == channel and sid == userId then&lt;br /&gt;
            -- If user pressed &amp;quot;-&amp;quot;, re-display the menu, else they picked a final color&lt;br /&gt;
            if msg ~= &amp;quot;-&amp;quot; then&lt;br /&gt;
                ll.Say(0, `{n} selected {msg}`)&lt;br /&gt;
                -- Now that they&#039;ve chosen something else, remove the listener and finish&lt;br /&gt;
                ll.ListenRemove(listenHandle)&lt;br /&gt;
                return&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    for i=0, num_detected-1 do&lt;br /&gt;
        local toucherId = ll.DetectedKey(i)&lt;br /&gt;
        -- Create a separate coroutine for each person who touches&lt;br /&gt;
        EventLoop:create_task(function()&lt;br /&gt;
            handle_dialog_for_user(toucherId)&lt;br /&gt;
        end)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Run state_entry on load:&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== More Examples ===&lt;br /&gt;
&lt;br /&gt;
Find more example scripts at [[Luau Example Scripts]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218019</id>
		<title>Lua Alpha</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218019"/>
		<updated>2025-03-05T01:07:41Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{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.}}&lt;br /&gt;
&lt;br /&gt;
= Second Life Luau Alpha =&lt;br /&gt;
&lt;br /&gt;
[[File:Luau.png|720px|thumb|right|Luau logo]]&lt;br /&gt;
&lt;br /&gt;
We&#039;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.&lt;br /&gt;
&lt;br /&gt;
== What is Luau? ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Why Luau? ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== How to Get Started with Luau in Second Life ==&lt;br /&gt;
&lt;br /&gt;
In order to play with Luau, you&#039;ll need to download our Lua project viewer, and log onto our Aditi beta grid.&lt;br /&gt;
&lt;br /&gt;
* Access the latest build of the Luau-enabled Second Life Viewer from [https://releasenotes.secondlife.com/viewer/7.1.12.13526902562.html here].&lt;br /&gt;
&lt;br /&gt;
Once you&#039;ve got the new viewer and have logged onto the beta grid, head over to these Luau-enabled regions:&lt;br /&gt;
&lt;br /&gt;
* [secondlife://Aditi/secondlife/Luau%20Yardang/241/235/27 Luau Yardang]&lt;br /&gt;
* [secondlife://Aditi/secondlife/Luau%20Tombolo/241/235/27 Luau Tombolo]&lt;br /&gt;
* [secondlife://Aditi/secondlife/Luau%20Mesa/241/235/27 Luau Mesa]&lt;br /&gt;
* [secondlife://Aditi/secondlife/Luau%20Tideland/241/235/27 Luau Tideland]&lt;br /&gt;
&lt;br /&gt;
When editing a script in the new Lua project viewer, you&#039;ll notice a new &#039;&#039;&#039;Compiler&#039;&#039;&#039; 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).&lt;br /&gt;
&lt;br /&gt;
[[File:Compiler_dropdown.png|Compiler selection dropdown]]&lt;br /&gt;
&lt;br /&gt;
Compiler drop-down options:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;LSL: Legacy (LSO2)&#039;&#039;&#039; - Scripts written in LSL, to be run on the old LSO2 VM&lt;br /&gt;
* &#039;&#039;&#039;LSL: Mono&#039;&#039;&#039;- Scripts written in LSL, to be run on the Mono VM&lt;br /&gt;
* &#039;&#039;&#039;Lua&#039;&#039;&#039; - Scripts written in Lua, to be run on the Luau VM&lt;br /&gt;
* &#039;&#039;&#039;LSL/Luau&#039;&#039;&#039;- Scripts written in LSL, to be run on the Luau VM&lt;br /&gt;
&lt;br /&gt;
=== Transitioning from LSL to Luau ===&lt;br /&gt;
* &#039;&#039;&#039;Function Namespacing:&#039;&#039;&#039;&lt;br /&gt;
** In Luau, Linden Lab functions have been moved under the &#039;&#039;&#039;ll&#039;&#039;&#039; namespace.&lt;br /&gt;
** For example:&lt;br /&gt;
*** &#039;&#039;llSay&#039;&#039; becomes &#039;&#039;ll.Say&#039;&#039;&lt;br /&gt;
*** &#039;&#039;llGetPos&#039;&#039; becomes &#039;&#039;ll.GetPos&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Constants:&#039;&#039;&#039;&lt;br /&gt;
** Constants like &#039;&#039;PERMISSION_TAKE_CONTROLS&#039;&#039; and &#039;&#039;PRIM_ROT_LOCAL&#039;&#039; are not predefined in Luau.&lt;br /&gt;
** You must specify these constants if you wish to reference them in your scripts.&lt;br /&gt;
&lt;br /&gt;
=== Luau Libraries ===&lt;br /&gt;
* &#039;&#039;&#039;Coroutines:&#039;&#039;&#039;&lt;br /&gt;
** Luau supports coroutines, allowing for cooperative multitasking within scripts.&lt;br /&gt;
** Key functions include:&lt;br /&gt;
*** &#039;&#039;coroutine.create&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.status&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.resume&#039;&#039;&lt;br /&gt;
** Refer to the [https://luau.org/library#coroutine-library coroutine library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Bitwise Operations:&#039;&#039;&#039;&lt;br /&gt;
** Luau includes a &#039;&#039;bit32&#039;&#039; library for bitwise operations, enabling more efficient data manipulation.&lt;br /&gt;
** Refer to the [https://luau.org/library#bit32-library bit32 library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Standard Library:&#039;&#039;&#039;&lt;br /&gt;
** Luau comes equipped with a standard library of functions designed to manipulate built-in data types.&lt;br /&gt;
** Explore the [https://luau.org/library Luau Standard Library] for a comprehensive list of available functions.&lt;br /&gt;
&lt;br /&gt;
== Feedback and Support ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== Example Scripts ==&lt;br /&gt;
&lt;br /&gt;
To help you get started, we&#039;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&#039;s desire!&lt;br /&gt;
&lt;br /&gt;
=== default_script.lua ===&lt;br /&gt;
This script is roughly equivalent to the &amp;quot;new script&amp;quot; that gets created.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function state_entry()&lt;br /&gt;
    ll.Say(0, &amp;quot;Hello, Avatar!&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(total_number)&lt;br /&gt;
   ll.Say(0, &amp;quot;Touched.&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dialog.lua ===&lt;br /&gt;
This script demonstrates how one can interact with dialog menus.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Define the menu buttons and dialog message.&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local ToucherID = nil&lt;br /&gt;
local dialogChannel = nil&lt;br /&gt;
local listenHandle = nil&lt;br /&gt;
&lt;br /&gt;
-- This function is called when the script first starts.&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Get the object&#039;s key and compute a dialog channel number.&lt;br /&gt;
    local key = ll.GetKey()&lt;br /&gt;
    -- Extract the last 7 characters of the key and convert it from hex.&lt;br /&gt;
    dialogChannel = -1 - tonumber(string.sub(tostring(key), -7, -1), 16)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    ToucherID = ll.DetectedKey(0)&lt;br /&gt;
    -- If there is already a listen handle, then remove it&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
    end&lt;br /&gt;
    listenHandle = ll.Listen(dialogChannel, &amp;quot;&amp;quot;, ToucherID, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
    -- Set a 60-second timer for response.&lt;br /&gt;
    ll.SetTimerEvent(60.0)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when a dialog response is received.&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    if message == &amp;quot;-&amp;quot; then&lt;br /&gt;
        -- Redisplay the dialog if the &amp;quot;-&amp;quot; option is selected.&lt;br /&gt;
        ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Stop the timer, and stop the listening handler.&lt;br /&gt;
    ll.ListenRemove(listenHandle)&lt;br /&gt;
    ll.SetTimerEvent(0)&lt;br /&gt;
    -- Let the user know what they selected&lt;br /&gt;
    ll.Say(0, `You selected {message}`)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the timer expires.&lt;br /&gt;
function timer()&lt;br /&gt;
    -- Stop the timer and clean up the listener.&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.SetTimerEvent(0)&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
        ll.Whisper(0, &amp;quot;Sorry. You snooze; you lose.&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dialog_coroutine.lua ===&lt;br /&gt;
This script demonstrates how one could use coroutines to handle dialog responses, with multi-user support.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Minimal EventLoop&lt;br /&gt;
-------------------------&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; eventName it’s waiting for&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status(coro) ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Internal helper: resumes a coroutine&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local ok, eventAwaited = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    if not ok then&lt;br /&gt;
        ll.OwnerSay(`Coroutine error: {eventAwaited}`)&lt;br /&gt;
        self._coros[coro] = nil&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- If still alive, &#039;eventAwaited&#039; is the next event it wants&lt;br /&gt;
    self._coros[coro] = eventAwaited&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:handle_event(eventName, ...)&lt;br /&gt;
    ll.OwnerSay(`Handling event {eventName}`)&lt;br /&gt;
    local snapshot = table.clone(self._coros)&lt;br /&gt;
    for coro, waitingFor in pairs(snapshot) do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        elseif waitingFor == eventName then&lt;br /&gt;
            ll.OwnerSay(`Dispatching event {eventName} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Coroutines use this to yield until an event&lt;br /&gt;
local function await_event(name)&lt;br /&gt;
    assert(EventLoop.running, &amp;quot;await_event called outside a coroutine!&amp;quot;)&lt;br /&gt;
    return coroutine.yield(name)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Script Logic&lt;br /&gt;
-------------------------&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- Use the chat listener to feed the event-loop&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    -- We handle all &#039;listen&#039; events via the event-loop&lt;br /&gt;
    EventLoop:handle_event(`listen_{channel}`, channel, name, sender_id, message)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the script starts&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Seed math.random so each new script run doesn’t repeat the same channels&lt;br /&gt;
    math.randomseed(ll.GetUnixTime())&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Script started with random channels for each user.&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- A coroutine function for a single user&#039;s dialog flow&lt;br /&gt;
local function handle_dialog_for_user(userId)&lt;br /&gt;
    -- Use a random channel&lt;br /&gt;
    local channel = math.random(0x1, 0xFFFF)&lt;br /&gt;
    -- Create a listener for that channel&lt;br /&gt;
    local listenHandle = ll.Listen(channel, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        -- Show the user a dialog&lt;br /&gt;
        ll.Dialog(userId, dialogInfo, buttons, channel)&lt;br /&gt;
&lt;br /&gt;
        -- Wait for the next &#039;listen&#039; event (channel, name, sender_id, message)&lt;br /&gt;
        local c, n, sid, msg = await_event(`listen_{channel}`)&lt;br /&gt;
&lt;br /&gt;
        -- If this &amp;quot;listen&amp;quot; event isn&#039;t for our channel/user, ignore it&lt;br /&gt;
        if c == channel and sid == userId then&lt;br /&gt;
            -- If user pressed &amp;quot;-&amp;quot;, re-display the menu, else they picked a final color&lt;br /&gt;
            if msg ~= &amp;quot;-&amp;quot; then&lt;br /&gt;
                ll.Say(0, `{n} selected {msg}`)&lt;br /&gt;
                -- Now that they&#039;ve chosen something else, remove the listener and finish&lt;br /&gt;
                ll.ListenRemove(listenHandle)&lt;br /&gt;
                return&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    for i=0, num_detected-1 do&lt;br /&gt;
        local toucherId = ll.DetectedKey(i)&lt;br /&gt;
        -- Create a separate coroutine for each person who touches&lt;br /&gt;
        EventLoop:create_task(function()&lt;br /&gt;
            handle_dialog_for_user(toucherId)&lt;br /&gt;
        end)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Run state_entry on load:&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== More Examples ===&lt;br /&gt;
&lt;br /&gt;
Find more example scripts at [[Luau Example Scripts]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Luau_Examples&amp;diff=1218017</id>
		<title>Luau Examples</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Luau_Examples&amp;diff=1218017"/>
		<updated>2025-03-04T23:05:47Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
Example [[Luau Alpha|Luau]] scripts&lt;br /&gt;
&lt;br /&gt;
== vehicle.lua ==&lt;br /&gt;
This script demonstrates how one may create their own vehicle scripts, which don’t rely on the underlying LL vehicle system. &lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Control and permission constants&lt;br /&gt;
local PERMISSION_TAKE_CONTROLS = 0x4&lt;br /&gt;
local CONTROL_FWD       = 0x1&lt;br /&gt;
local CONTROL_BACK      = 0x2&lt;br /&gt;
local CONTROL_UP        = 0x10&lt;br /&gt;
local CONTROL_DOWN      = 0x20&lt;br /&gt;
local CONTROL_LEFT      = 0x4&lt;br /&gt;
local CONTROL_RIGHT     = 0x8&lt;br /&gt;
local PRIM_ROT_LOCAL    = integer(29)&lt;br /&gt;
&lt;br /&gt;
-- These two will hold the link numbers for left and right front wheels&lt;br /&gt;
local LINK_WHEEL_LEFT = 0&lt;br /&gt;
local LINK_WHEEL_RIGHT = 0&lt;br /&gt;
&lt;br /&gt;
-- Integra Type R Engine properties&lt;br /&gt;
local Engine = {&lt;br /&gt;
    rpm = 800,              -- current engine RPM (starts at idle)&lt;br /&gt;
    gear = 0,               -- 0 = neutral; -1 = reverse; 1-5 for forward gears&lt;br /&gt;
    idleRPM = 800,          -- idle RPM&lt;br /&gt;
    maxRPM = 8300,          -- redline&lt;br /&gt;
    throttle = 0.0,         -- throttle value (0.0 to 1.0)&lt;br /&gt;
    gearRatios = {          -- Integra Type R gear ratios&lt;br /&gt;
        [-1] = 3.727,       -- reverse gear ratio&lt;br /&gt;
        [0]  = 0,          -- neutral&lt;br /&gt;
        [1]  = 3.727,&lt;br /&gt;
        [2]  = 2.256,&lt;br /&gt;
        [3]  = 1.729,&lt;br /&gt;
        [4]  = 1.416,&lt;br /&gt;
        [5]  = 1.194&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FINAL_DRIVE = 4.1   -- Final drive ratio&lt;br /&gt;
local WHEEL_RADIUS = 0.3  -- in meters&lt;br /&gt;
&lt;br /&gt;
-- Vehicle properties (using realistic Integra Type R mass)&lt;br /&gt;
local Vehicle = {&lt;br /&gt;
    speed = 0,      -- speed in m/s; negative indicates reverse motion&lt;br /&gt;
    mass = 1200,    -- mass in kg&lt;br /&gt;
    angle = 0,      -- heading angle in radians&lt;br /&gt;
    steering = 0,   -- steering input: -1 (left), 0 (none), 1 (right)&lt;br /&gt;
    brake = false   -- flag indicating if brakes are applied&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Engine constants (using realistic Integra Type R values)&lt;br /&gt;
local MAX_TORQUE = 200   -- in Nm&lt;br /&gt;
&lt;br /&gt;
-- Brake force constant (for active braking)&lt;br /&gt;
local BRAKE_FORCE = 4000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Define a torque curve function:&lt;br /&gt;
local function torqueCurve(rpm)&lt;br /&gt;
    local peakRPM = 6000&lt;br /&gt;
    if rpm &amp;lt; peakRPM then&lt;br /&gt;
        return 0.8 + 0.2 * ((rpm - Engine.idleRPM) / (peakRPM - Engine.idleRPM))&lt;br /&gt;
    else&lt;br /&gt;
        local factor = 1 - 0.5 * ((rpm - peakRPM) / (Engine.maxRPM - peakRPM))&lt;br /&gt;
        return math.max(0.5, factor)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Engine braking force when throttle is zero in gear.&lt;br /&gt;
local ENGINE_BRAKE_FORCE = 2000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Friction and drag constants&lt;br /&gt;
local G = 9.81            -- gravity (m/s^2)&lt;br /&gt;
local C_rr_inGear = 0.015 -- rolling resistance coefficient in gear&lt;br /&gt;
local C_rr_neutral = 0.05 -- rolling resistance coefficient in neutral&lt;br /&gt;
&lt;br /&gt;
local airDensity = 1.225      -- kg/m^3&lt;br /&gt;
local dragCoefficient = 0.3   -- aerodynamic drag coefficient&lt;br /&gt;
local frontalArea = 2.2       -- in m^2&lt;br /&gt;
&lt;br /&gt;
local function getAerodynamicDrag(speed)&lt;br /&gt;
    return 0.5 * airDensity * dragCoefficient * frontalArea * speed * speed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Compute engine RPM from vehicle speed when in gear.&lt;br /&gt;
local function computeEngineRPMFromSpeed()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return Engine.rpm  -- In neutral, RPM is managed independently.&lt;br /&gt;
    else&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local rpm = math.abs(Vehicle.speed) * conversionFactor&lt;br /&gt;
        if rpm &amp;lt; Engine.idleRPM then rpm = Engine.idleRPM end&lt;br /&gt;
        return rpm&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- For neutral, update RPM toward a target based on throttle.&lt;br /&gt;
local function updateEngineRPMNeutral()&lt;br /&gt;
    local targetRPM = Engine.idleRPM + (Engine.maxRPM - Engine.idleRPM) * Engine.throttle&lt;br /&gt;
    local changeRate = 200  -- RPM per tick&lt;br /&gt;
    if Engine.rpm &amp;lt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.min(Engine.rpm + changeRate, targetRPM)&lt;br /&gt;
    elseif Engine.rpm &amp;gt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.max(Engine.rpm - changeRate, targetRPM)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Calculate the engine force delivered to the wheels.&lt;br /&gt;
local function getEngineForce()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return 0  -- In neutral, no drive force is transmitted.&lt;br /&gt;
    else&lt;br /&gt;
        if Engine.throttle == 0 then&lt;br /&gt;
            -- Apply engine braking when throttle is released.&lt;br /&gt;
            return -ENGINE_BRAKE_FORCE&lt;br /&gt;
        else&lt;br /&gt;
            local torque = Engine.throttle * MAX_TORQUE * torqueCurve(Engine.rpm)&lt;br /&gt;
            local force = (torque * Engine.gearRatios[Engine.gear] * FINAL_DRIVE) / WHEEL_RADIUS&lt;br /&gt;
            return force&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Starts the engine by setting it to idle RPM.&lt;br /&gt;
local function startEngine()&lt;br /&gt;
    Engine.rpm = Engine.idleRPM&lt;br /&gt;
    ll.Say(0, &amp;quot;Engine started. RPM: &amp;quot; .. Engine.rpm)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Shifts the gear if valid.&lt;br /&gt;
local function shiftGear(newGear)&lt;br /&gt;
    if Engine.gearRatios[newGear] then&lt;br /&gt;
        Engine.gear = newGear&lt;br /&gt;
        ll.Say(0, &amp;quot;Shifted to gear &amp;quot; .. newGear)&lt;br /&gt;
    else&lt;br /&gt;
        ll.Say(0, &amp;quot;Invalid gear: &amp;quot; .. newGear)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function findWheels()&lt;br /&gt;
    local total = ll.GetNumberOfPrims()&lt;br /&gt;
    for i = 1, total do&lt;br /&gt;
        local name = ll.GetLinkName(i)&lt;br /&gt;
        if name == &amp;quot;WHEEL_LF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_LEFT = i&lt;br /&gt;
        elseif name == &amp;quot;WHEEL_RF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_RIGHT = i&lt;br /&gt;
        end&lt;br /&gt;
    end &lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local castRayCounter = 0&lt;br /&gt;
local lastGrounded = false&lt;br /&gt;
&lt;br /&gt;
local function isVehicleOnGround()&lt;br /&gt;
    castRayCounter = castRayCounter + 1&lt;br /&gt;
    if castRayCounter &amp;gt;= 5 then&lt;br /&gt;
        castRayCounter = 0&lt;br /&gt;
        local pos = ll.GetPos()&lt;br /&gt;
        local rayDistance = 4.5  -- threshold distance (in meters) to consider as &amp;quot;grounded&amp;quot;&lt;br /&gt;
        local rayStart = pos&lt;br /&gt;
        local rayEnd = pos + vector(0, 0, -rayDistance)&lt;br /&gt;
        local hitResult = ll.CastRay(rayStart, rayEnd, {})&lt;br /&gt;
        if table.getn(hitResult) == 3 then&lt;br /&gt;
            lastGrounded = true&lt;br /&gt;
        else&lt;br /&gt;
            lastGrounded = false&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return lastGrounded&lt;br /&gt;
end &lt;br /&gt;
&lt;br /&gt;
-- Timer event: updates engine, vehicle physics, steering, and rotates the wheels based on steering.&lt;br /&gt;
function timer()&lt;br /&gt;
    local dt = 0.1  -- time step in seconds&lt;br /&gt;
&lt;br /&gt;
    -- For neutral, update RPM based on throttle.&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        updateEngineRPMNeutral()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Calculate forces.&lt;br /&gt;
    local C_rr = (Engine.gear == 0) and C_rr_neutral or C_rr_inGear&lt;br /&gt;
    local F_roll = Vehicle.mass * G * C_rr&lt;br /&gt;
    local engineForce = getEngineForce()&lt;br /&gt;
    local dragForce = getAerodynamicDrag(math.abs(Vehicle.speed))&lt;br /&gt;
    local netForce = engineForce - (F_roll + dragForce)&lt;br /&gt;
&lt;br /&gt;
    -- Apply braking force if brakes are engaged.&lt;br /&gt;
    if Vehicle.brake then&lt;br /&gt;
        netForce = netForce - BRAKE_FORCE&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local acceleration = netForce / Vehicle.mass&lt;br /&gt;
    local vel = ll.GetVel()&lt;br /&gt;
    local horizontal_vec = vector(vel.x, vel.y, 0) &lt;br /&gt;
    Vehicle.speed = (ll.VecMag(horizontal_vec) + acceleration * dt)&lt;br /&gt;
&lt;br /&gt;
    -- Limit vehicle speed based on gear.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        if Vehicle.speed &amp;lt; 0 then &lt;br /&gt;
            Vehicle.speed = 0 &lt;br /&gt;
        end&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local maxSpeed = Engine.maxRPM / conversionFactor&lt;br /&gt;
        if Vehicle.speed &amp;gt; maxSpeed then&lt;br /&gt;
            Vehicle.speed = maxSpeed&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- When in gear, update RPM based on the new speed.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        Engine.rpm = computeEngineRPMFromSpeed()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Steering update using angular velocity and quaternions:&lt;br /&gt;
    local STEERING_RATE = math.rad(6)  -- desired steering rate per tick (radians)&lt;br /&gt;
    local yawDelta = Vehicle.steering * STEERING_RATE  -- small rotation for this tick&lt;br /&gt;
&lt;br /&gt;
    if yawDelta ~= 0 then&lt;br /&gt;
        local deltaQuat = ll.Euler2Rot(vector(0, 0, yawDelta))&lt;br /&gt;
        local angularSpeed = yawDelta / dt&lt;br /&gt;
        local angularVelocity = vector(0, 0, angularSpeed)&lt;br /&gt;
        ll.SetAngularVelocity(angularVelocity, 1)&lt;br /&gt;
        Vehicle.angle = Vehicle.angle + yawDelta&lt;br /&gt;
    else&lt;br /&gt;
        ll.SetAngularVelocity(vector(0, 0, 0), 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Display engine RPM and speed (converted to mph).&lt;br /&gt;
    local speed_mph = math.abs(Vehicle.speed) * 2.23694&lt;br /&gt;
    ll.SetText(&amp;quot;RPM: &amp;quot; .. math.floor(Engine.rpm) ..&lt;br /&gt;
               &amp;quot; | Speed: &amp;quot; .. string.format(&amp;quot;%.1f&amp;quot;, speed_mph) ..&lt;br /&gt;
               &amp;quot; mph&amp;quot;, vector(0,0,0), 1)&lt;br /&gt;
&lt;br /&gt;
    -- Only update velocity if the vehicle is near the ground, and we&#039;re in gear.&lt;br /&gt;
    if isVehicleOnGround() and Engine.gear ~= 0 then&lt;br /&gt;
        local velocityVector&lt;br /&gt;
        if Engine.gear &amp;gt; 0 then&lt;br /&gt;
            velocityVector = vector(Vehicle.speed, 0, 0)&lt;br /&gt;
        else&lt;br /&gt;
            -- Reverse gear&lt;br /&gt;
            velocityVector = vector(-Vehicle.speed, 0, 0)&lt;br /&gt;
        end&lt;br /&gt;
        ll.SetVelocity(velocityVector, 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Update the wheel rotations to visually match the steering.&lt;br /&gt;
    local maxWheelTurn = math.rad(30)  -- maximum wheel turn angle in radians&lt;br /&gt;
    local wheelAngle = -Vehicle.steering * maxWheelTurn&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_LEFT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_LEFT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(-4.71239, -wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_RIGHT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_RIGHT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(4.71239, wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched: starts engine and simulation.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    ll.Say(0, &amp;quot;Touch detected, requesting controls and starting engine.&amp;quot;)&lt;br /&gt;
    ll.RequestPermissions(ll.GetOwner(), PERMISSION_TAKE_CONTROLS)&lt;br /&gt;
    startEngine()&lt;br /&gt;
    findWheels()&lt;br /&gt;
    ll.SetTimerEvent(0.1)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when runtime permissions are granted.&lt;br /&gt;
function run_time_permissions(perm)&lt;br /&gt;
    if bit32.band(perm, PERMISSION_TAKE_CONTROLS) then&lt;br /&gt;
        ll.Say(0, &amp;quot;Permissions granted.&amp;quot;)&lt;br /&gt;
        local controlMask = bit32.bor(CONTROL_FWD, CONTROL_BACK, CONTROL_UP, CONTROL_DOWN, CONTROL_LEFT, CONTROL_RIGHT)&lt;br /&gt;
        ll.TakeControls(controlMask, 1, 1)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Handle control events:&lt;br /&gt;
-- • CONTROL_FWD applies throttle.&lt;br /&gt;
-- • CONTROL_BACK applies the brakes.&lt;br /&gt;
-- • CONTROL_UP and CONTROL_DOWN shift gears.&lt;br /&gt;
-- • CONTROL_LEFT and CONTROL_RIGHT steer the vehicle.&lt;br /&gt;
function control(avatar_id, level, edge)&lt;br /&gt;
    local start = bit32.band(level, edge)            &lt;br /&gt;
    local finish = bit32.band(bit32.bnot(level), edge) &lt;br /&gt;
    local held = bit32.band(level, bit32.bnot(edge))   &lt;br /&gt;
    local untouched = bit32.bnot(bit32.bor(level, edge)) &lt;br /&gt;
&lt;br /&gt;
    if bit32.band(held, CONTROL_BACK) ~= 0 then&lt;br /&gt;
        Vehicle.brake = true&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    elseif bit32.band(held, CONTROL_FWD) ~= 0 then&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 1.0&lt;br /&gt;
        if Engine.gear == 0 then&lt;br /&gt;
            updateEngineRPMNeutral()&lt;br /&gt;
        end&lt;br /&gt;
    else&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_UP) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear + 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_DOWN) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear - 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local steeringInput = 0&lt;br /&gt;
    if bit32.band(held, CONTROL_LEFT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput + 1&lt;br /&gt;
    end&lt;br /&gt;
    if bit32.band(held, CONTROL_RIGHT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput - 1&lt;br /&gt;
    end&lt;br /&gt;
    Vehicle.steering = steeringInput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== weather_box.lua ==&lt;br /&gt;
This script demonstrates coroutines, as well as working with a JSON-based web API:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local TEXTBOX_CHANNEL = 323242&lt;br /&gt;
-- kludge because we don&#039;t have LSL constants in here yet&lt;br /&gt;
local HTTP_BODY_MAXLENGTH = integer(2)&lt;br /&gt;
local PRIM_TEXT = integer(26)&lt;br /&gt;
local PRIM_SIZE = integer(7)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
gCityName = &amp;quot;&amp;quot;&lt;br /&gt;
gLatLong = nil  -- A table of 2 elems when it&#039;s populated&lt;br /&gt;
gCityRequestTask = nil&lt;br /&gt;
gWeatherRequestTask = nil&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- JSON stuff&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- From https://github.com/rxi/json.lua/blob/master/json.lua&lt;br /&gt;
&lt;br /&gt;
local json = {}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map = {&lt;br /&gt;
  [ &amp;quot;\\&amp;quot; ] = &amp;quot;\\&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\&amp;quot;&amp;quot; ] = &amp;quot;\&amp;quot;&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\b&amp;quot; ] = &amp;quot;b&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\f&amp;quot; ] = &amp;quot;f&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\n&amp;quot; ] = &amp;quot;n&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\r&amp;quot; ] = &amp;quot;r&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\t&amp;quot; ] = &amp;quot;t&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map_inv = { [ &amp;quot;/&amp;quot; ] = &amp;quot;/&amp;quot; }&lt;br /&gt;
for k, v in pairs(escape_char_map) do&lt;br /&gt;
  escape_char_map_inv[v] = k&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local parse&lt;br /&gt;
&lt;br /&gt;
local function create_set(...)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  for i = 1, select(&amp;quot;#&amp;quot;, ...) do&lt;br /&gt;
    res[ select(i, ...) ] = true&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local space_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;)&lt;br /&gt;
local delim_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, &amp;quot;]&amp;quot;, &amp;quot;}&amp;quot;, &amp;quot;,&amp;quot;)&lt;br /&gt;
local escape_chars  = create_set(&amp;quot;\\&amp;quot;, &amp;quot;/&amp;quot;, &#039;&amp;quot;&#039;, &amp;quot;b&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;r&amp;quot;, &amp;quot;t&amp;quot;, &amp;quot;u&amp;quot;)&lt;br /&gt;
local literals      = create_set(&amp;quot;true&amp;quot;, &amp;quot;false&amp;quot;, &amp;quot;null&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local literal_map = {&lt;br /&gt;
  [ &amp;quot;true&amp;quot;  ] = true,&lt;br /&gt;
  [ &amp;quot;false&amp;quot; ] = false,&lt;br /&gt;
  [ &amp;quot;null&amp;quot;  ] = nil,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function next_char(str, idx, set, negate)&lt;br /&gt;
  for i = idx, #str do&lt;br /&gt;
    if set[str:sub(i, i)] ~= negate then&lt;br /&gt;
      return i&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  return #str + 1&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function decode_error(str, idx, msg)&lt;br /&gt;
  local line_count = 1&lt;br /&gt;
  local col_count = 1&lt;br /&gt;
  for i = 1, idx - 1 do&lt;br /&gt;
    col_count = col_count + 1&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;\n&amp;quot; then&lt;br /&gt;
      line_count = line_count + 1&lt;br /&gt;
      col_count = 1&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;%s at line %d col %d&amp;quot;, msg, line_count, col_count) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function codepoint_to_utf8(n)&lt;br /&gt;
  -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&amp;amp;id=iws-appendixa&lt;br /&gt;
  local f = math.floor&lt;br /&gt;
  if n &amp;lt;= 0x7f then&lt;br /&gt;
    return string.char(n)&lt;br /&gt;
  elseif n &amp;lt;= 0x7ff then&lt;br /&gt;
    return string.char(f(n / 64) + 192, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0xffff then&lt;br /&gt;
    return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0x10ffff then&lt;br /&gt;
    return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,&lt;br /&gt;
                       f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;invalid unicode codepoint &#039;%x&#039;&amp;quot;, n) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_unicode_escape(s)&lt;br /&gt;
  local n1 = tonumber( s:sub(1, 4),  16 )&lt;br /&gt;
  local n2 = tonumber( s:sub(7, 10), 16 )&lt;br /&gt;
   -- Surrogate pair?&lt;br /&gt;
  if n2 then&lt;br /&gt;
    return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)&lt;br /&gt;
  else&lt;br /&gt;
    return codepoint_to_utf8(n1)&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_string(str, i)&lt;br /&gt;
  local res = &amp;quot;&amp;quot;&lt;br /&gt;
  local j = i + 1&lt;br /&gt;
  local k = j&lt;br /&gt;
&lt;br /&gt;
  while j &amp;lt;= #str do&lt;br /&gt;
    local x = str:byte(j)&lt;br /&gt;
&lt;br /&gt;
    if x &amp;lt; 32 then&lt;br /&gt;
      decode_error(str, j, &amp;quot;control character in string&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    elseif x == 92 then -- `\`: Escape&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      j = j + 1&lt;br /&gt;
      local c = str:sub(j, j)&lt;br /&gt;
      if c == &amp;quot;u&amp;quot; then&lt;br /&gt;
        local hex = str:match(&amp;quot;^[dD][89aAbB]%x%x\\u%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or str:match(&amp;quot;^%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or decode_error(str, j - 1, &amp;quot;invalid unicode escape in string&amp;quot;)&lt;br /&gt;
        res = res .. parse_unicode_escape(hex)&lt;br /&gt;
        j = j + #hex&lt;br /&gt;
      else&lt;br /&gt;
        if not escape_chars[c] then&lt;br /&gt;
          decode_error(str, j - 1, &amp;quot;invalid escape char &#039;&amp;quot; .. c .. &amp;quot;&#039; in string&amp;quot;)&lt;br /&gt;
        end&lt;br /&gt;
        res = res .. escape_char_map_inv[c]&lt;br /&gt;
      end&lt;br /&gt;
      k = j + 1&lt;br /&gt;
&lt;br /&gt;
    elseif x == 34 then -- `&amp;quot;`: End of string&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      return res, j + 1&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    j = j + 1&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  decode_error(str, i, &amp;quot;expected closing quote for string&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_number(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local s = str:sub(i, x - 1)&lt;br /&gt;
  local n = tonumber(s)&lt;br /&gt;
  if not n then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid number &#039;&amp;quot; .. s .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return n, x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_literal(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local word = str:sub(i, x - 1)&lt;br /&gt;
  if not literals[word] then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid literal &#039;&amp;quot; .. word .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return literal_map[word], x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_array(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  local n = 1&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local x&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of array?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;]&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read token&lt;br /&gt;
    x, i = parse(str, i)&lt;br /&gt;
    res[n] = x&lt;br /&gt;
    n = n + 1&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;]&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;]&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_object(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local key, val&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of object?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;}&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read key&lt;br /&gt;
    if str:sub(i, i) ~= &#039;&amp;quot;&#039; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected string for key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    key, i = parse(str, i)&lt;br /&gt;
    -- Read &#039;:&#039; delimiter&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    if str:sub(i, i) ~= &amp;quot;:&amp;quot; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected &#039;:&#039; after key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    i = next_char(str, i + 1, space_chars, true)&lt;br /&gt;
    -- Read value&lt;br /&gt;
    val, i = parse(str, i)&lt;br /&gt;
    -- Set&lt;br /&gt;
    res[key] = val&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;}&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;}&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local char_func_map = {&lt;br /&gt;
  [ &#039;&amp;quot;&#039; ] = parse_string,&lt;br /&gt;
  [ &amp;quot;0&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;1&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;2&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;3&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;4&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;5&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;6&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;7&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;8&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;9&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;-&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;t&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;f&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;n&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;[&amp;quot; ] = parse_array,&lt;br /&gt;
  [ &amp;quot;{&amp;quot; ] = parse_object,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
parse = function(str, idx)&lt;br /&gt;
  local chr = str:sub(idx, idx)&lt;br /&gt;
  local f = char_func_map[chr]&lt;br /&gt;
  if f then&lt;br /&gt;
    return f(str, idx)&lt;br /&gt;
  end&lt;br /&gt;
  decode_error(str, idx, &amp;quot;unexpected character &#039;&amp;quot; .. chr .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function json.decode(str)&lt;br /&gt;
  if type(str) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
    error(&amp;quot;expected argument of type string, got &amp;quot; .. type(str))&lt;br /&gt;
  end&lt;br /&gt;
  local res, idx = parse(str, next_char(str, 1, space_chars, true))&lt;br /&gt;
  idx = next_char(str, idx, space_chars, true)&lt;br /&gt;
  if idx &amp;lt;= #str then&lt;br /&gt;
    decode_error(str, idx, &amp;quot;trailing garbage&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Loop&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- A naive little event loop that lets you track running coroutines and dispatches&lt;br /&gt;
-- the events they&#039;re waiting for to them.&lt;br /&gt;
&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; awaited event&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    -- false has no semantic meaning here, we just want to reserve the space for&lt;br /&gt;
    -- the coroutine in the table without saying we&#039;re awaiting anything yet.&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- (internal) run the event&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    assert(coroutine.status(coro) ~= &amp;quot;dead&amp;quot;)&lt;br /&gt;
    -- assert(not self.running)&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    -- Since we&#039;re running, this isn&#039;t currently awaiting anything&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local success, ret = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    -- If we&#039;re not done then ret should be the kind of event we&#039;re awaiting.&lt;br /&gt;
    -- Otherwise it&#039;s an error message or something. Who cares.&lt;br /&gt;
    if not success then&lt;br /&gt;
        -- Get rid of the coro&lt;br /&gt;
        if ret then&lt;br /&gt;
            ll.OwnerSay(`Might have an error: {ret}`)&lt;br /&gt;
        end&lt;br /&gt;
        ret = nil&lt;br /&gt;
    end&lt;br /&gt;
    self._coros[coro] = ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Wake up any tasks that are waiting for this kind of event&lt;br /&gt;
function EventLoop:handle_event(name, ...)&lt;br /&gt;
    -- We may mutate self._coros, so do a clone&lt;br /&gt;
    local coros = table.clone(self._coros)&lt;br /&gt;
    ll.OwnerSay(`Handling {name}`)&lt;br /&gt;
&lt;br /&gt;
    for coro, awaited_event in coros do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            -- Hmm, this coroutine is dead, prune it.&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if awaited_event == name then&lt;br /&gt;
            ll.OwnerSay(`Dispatching {name} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_event_catcher(name)&lt;br /&gt;
    local function catcher(...)&lt;br /&gt;
        ll.OwnerSay(&amp;quot;yay&amp;quot;)&lt;br /&gt;
        EventLoop:handle_event(name, ...)&lt;br /&gt;
    end&lt;br /&gt;
    return catcher&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Helper function for waiting and telling the EventLoop what event you want&lt;br /&gt;
local function await_event(kind)&lt;br /&gt;
    -- You had better do this inside a running event loop&lt;br /&gt;
    assert(EventLoop.running)&lt;br /&gt;
    -- This will yield, the EventLoop will resume us once it has&lt;br /&gt;
    -- an event we might be interested in.&lt;br /&gt;
    return coroutine.yield(kind)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Script-specific functions&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
local function hide_children()&lt;br /&gt;
    for i=2,ll.GetNumberOfPrims() do&lt;br /&gt;
        -- Hide all the child prims&lt;br /&gt;
        ll.SetLinkAlpha(i, 0, -1)&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(i, {&lt;br /&gt;
            PRIM_SIZE, vector(0.5, 0.5, 0.5),&lt;br /&gt;
            PRIM_TEXT, &amp;quot;&amp;quot;, vector(1,1,1), 1&lt;br /&gt;
        })&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_textbox(avatar_id, message, channel)&lt;br /&gt;
    ll.TextBox(avatar_id, message, channel)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        local resp_channel, name, id, resp = await_event(&amp;quot;listen&amp;quot;)&lt;br /&gt;
        if id ~= avatar_id or channel ~= resp_channel then&lt;br /&gt;
            -- If this isn&#039;t a message we&#039;re interested in then wait for the next event&lt;br /&gt;
            ll.OwnerSay(`{id}, {avatar_id}, {resp_channel}, {channel}`)&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return resp&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function make_json_request(url)&lt;br /&gt;
    local req_id = ll.HTTPRequest(url, {HTTP_BODY_MAXLENGTH, integer(16000)}, &amp;quot;&amp;quot;)&lt;br /&gt;
    while true do&lt;br /&gt;
        local ev_id, status, metadata, body = await_event(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
        if ev_id ~= req_id then&lt;br /&gt;
            -- Not a response for the HTTP request we were waiting on, wait for the next event.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return status, metadata, body&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function request_weather()&lt;br /&gt;
    -- And you can use a wrapper like this to make an async function callable within both normal and coroutine contexts.&lt;br /&gt;
    -- Normally this sort of thing would not be necessary, but here it is since the EventLoop lives in our own code,&lt;br /&gt;
    -- and isn&#039;t a property of the actual script engine.&lt;br /&gt;
    if gWeatherRequestTask then&lt;br /&gt;
        EventLoop:kill_task(gWeatherRequestTask)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    gWeatherRequestTask = EventLoop:create_task(function()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Requesting weather&amp;quot;)&lt;br /&gt;
        local status, metadata, body = make_json_request(`https://api.open-meteo.com/v1/forecast?latitude={gLatLong[1]}&amp;amp;longitude={gLatLong[2]}&amp;amp;current=temperature_2m&amp;amp;daily=temperature_2m_max`)&lt;br /&gt;
        if status ~= 200 then&lt;br /&gt;
            ll.OwnerSay(&amp;quot;Error requesting weather&amp;quot;)&lt;br /&gt;
            return&lt;br /&gt;
        end&lt;br /&gt;
        local parsed = json.decode(body)&lt;br /&gt;
&lt;br /&gt;
        local current = parsed[&amp;quot;current&amp;quot;]&lt;br /&gt;
        ll.OwnerSay(`Temperature is {current[&amp;quot;temperature_2m&amp;quot;]}C`)&lt;br /&gt;
        ll.SetText(`Temperature in {gCityName} is currently {current[&amp;quot;temperature_2m&amp;quot;]}C`, vector(1,1,1), 1)&lt;br /&gt;
&lt;br /&gt;
        -- Show the highs for each day&lt;br /&gt;
        local days = parsed[&amp;quot;daily&amp;quot;][&amp;quot;temperature_2m_max&amp;quot;]&lt;br /&gt;
        for i=1,#days do&lt;br /&gt;
            local link_num = i + 1&lt;br /&gt;
            -- Higher temps = bigger on Z, negatives are unrepresentable :)&lt;br /&gt;
            local z_size = (days[i] / 30) * 2&lt;br /&gt;
            ll.SetLinkAlpha(link_num, 1, -1)&lt;br /&gt;
            ll.SetLinkPrimitiveParamsFast(link_num, {&lt;br /&gt;
                PRIM_SIZE, vector(0.5, 0.5, z_size),&lt;br /&gt;
                PRIM_TEXT, `{days[i]}C`, vector(1,1,1), 1&lt;br /&gt;
            })&lt;br /&gt;
        end&lt;br /&gt;
    end)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ask_for_city()&lt;br /&gt;
    local resp = send_textbox(ll.GetOwner(), &amp;quot;What city do you want the weather for?&amp;quot;, TEXTBOX_CHANNEL)&lt;br /&gt;
    if not resp then&lt;br /&gt;
        ll.OwnerSay(&amp;quot;Got no response for city request.&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    ll.OwnerSay(`You want the weather for {resp}.`)&lt;br /&gt;
    gLatLong = nil&lt;br /&gt;
    gCityName = resp&lt;br /&gt;
&lt;br /&gt;
    hide_children()&lt;br /&gt;
&lt;br /&gt;
    -- request the lat and long for this city&lt;br /&gt;
    local status, metadata, body = make_json_request(`https://nominatim.openstreetmap.org/search.php?city={ll.EscapeURL(resp)}&amp;amp;format=jsonv2`)&lt;br /&gt;
    local parsed = json.decode(body)&lt;br /&gt;
    if not #parsed then&lt;br /&gt;
        ll.OwnerSay(`Got no results for {resp}!`)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local first_result = parsed[1]&lt;br /&gt;
    gLatLong = {first_result[&amp;quot;lat&amp;quot;], first_result[&amp;quot;lon&amp;quot;]}&lt;br /&gt;
    ll.OwnerSay(`City Lat: {gLatLong[1]}, Long: {gLatLong[2]}`)&lt;br /&gt;
&lt;br /&gt;
    request_weather()&lt;br /&gt;
&lt;br /&gt;
    -- request weather again in 3 mins&lt;br /&gt;
    ll.SetTimerEvent(180)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Handlers&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- If we don&#039;t care about conventionally dispatched events, we can just set the event handler to directly send&lt;br /&gt;
-- event data to the event loop.&lt;br /&gt;
http_response = EventLoop:create_event_catcher(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
listen = EventLoop:create_event_catcher(&amp;quot;listen&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- More conventional event handlers work too.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    for i=0,num-1 do&lt;br /&gt;
        if ll.GetOwner() ~= ll.DetectedKey(i) then&lt;br /&gt;
            -- Go to next loop if it wasn&#039;t the owner touching us.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if gCityRequestTask then&lt;br /&gt;
            EventLoop:kill_task(gCityRequestTask)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Spin up a task to ask the user for their city&lt;br /&gt;
        gCityRequestTask = EventLoop:create_task(ask_for_city)&lt;br /&gt;
        break&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function timer()&lt;br /&gt;
    request_weather()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Not strictly necessary, but I like having the main function have its own scope.&lt;br /&gt;
-- Rather than just have top-level logic.&lt;br /&gt;
local function main()&lt;br /&gt;
    ll.Listen(TEXTBOX_CHANNEL, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.SetText(&amp;quot;&amp;quot;, vector(1,1,1), 1)&lt;br /&gt;
    hide_children()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Weatherbox operational!&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
main()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218016</id>
		<title>Lua Alpha</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218016"/>
		<updated>2025-03-04T23:05:08Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{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.}}&lt;br /&gt;
&lt;br /&gt;
= Second Life Luau Alpha =&lt;br /&gt;
&lt;br /&gt;
[[File:Luau.png|720px|thumb|right|Luau logo]]&lt;br /&gt;
&lt;br /&gt;
We&#039;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.&lt;br /&gt;
&lt;br /&gt;
== What is Luau? ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Why Luau? ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== How to Get Started with Luau in Second Life ==&lt;br /&gt;
&lt;br /&gt;
In order to play with Luau, you&#039;ll need to download our Lua project viewer, and log onto our Aditi beta grid.&lt;br /&gt;
&lt;br /&gt;
* Access the latest build of the Luau-enabled Second Life Viewer from [https://releasenotes.secondlife.com/viewer/7.1.12.13526902562.html here].&lt;br /&gt;
&lt;br /&gt;
Once you&#039;ve got the new viewer and have logged onto the beta grid, head over to these Luau-enabled regions:&lt;br /&gt;
* Luau Yardang&lt;br /&gt;
* Luau Tombolo&lt;br /&gt;
* Luau Mesa&lt;br /&gt;
* Luau Tideland&lt;br /&gt;
&lt;br /&gt;
When editing a script in the new Lua project viewer, you&#039;ll notice a new &#039;&#039;&#039;Compiler&#039;&#039;&#039; 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).&lt;br /&gt;
&lt;br /&gt;
[[File:Compiler_dropdown.png|Compiler selection dropdown]]&lt;br /&gt;
&lt;br /&gt;
Compiler drop-down options:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;LSL: Legacy (LSO2)&#039;&#039;&#039; - Scripts written in LSL, to be run on the old LSO2 VM&lt;br /&gt;
* &#039;&#039;&#039;LSL: Mono&#039;&#039;&#039;- Scripts written in LSL, to be run on the Mono VM&lt;br /&gt;
* &#039;&#039;&#039;Lua&#039;&#039;&#039; - Scripts written in Lua, to be run on the Luau VM&lt;br /&gt;
* &#039;&#039;&#039;LSL/Luau&#039;&#039;&#039;- Scripts written in LSL, to be run on the Luau VM&lt;br /&gt;
&lt;br /&gt;
=== Transitioning from LSL to Luau ===&lt;br /&gt;
* &#039;&#039;&#039;Function Namespacing:&#039;&#039;&#039;&lt;br /&gt;
** In Luau, Linden Lab functions have been moved under the &#039;&#039;&#039;ll&#039;&#039;&#039; namespace.&lt;br /&gt;
** For example:&lt;br /&gt;
*** &#039;&#039;llSay&#039;&#039; becomes &#039;&#039;ll.Say&#039;&#039;&lt;br /&gt;
*** &#039;&#039;llGetPos&#039;&#039; becomes &#039;&#039;ll.GetPos&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Constants:&#039;&#039;&#039;&lt;br /&gt;
** Constants like &#039;&#039;PERMISSION_TAKE_CONTROLS&#039;&#039; and &#039;&#039;PRIM_ROT_LOCAL&#039;&#039; are not predefined in Luau.&lt;br /&gt;
** You must specify these constants if you wish to reference them in your scripts.&lt;br /&gt;
&lt;br /&gt;
=== Luau Libraries ===&lt;br /&gt;
* &#039;&#039;&#039;Coroutines:&#039;&#039;&#039;&lt;br /&gt;
** Luau supports coroutines, allowing for cooperative multitasking within scripts.&lt;br /&gt;
** Key functions include:&lt;br /&gt;
*** &#039;&#039;coroutine.create&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.status&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.resume&#039;&#039;&lt;br /&gt;
** Refer to the [https://luau.org/library#coroutine-library coroutine library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Bitwise Operations:&#039;&#039;&#039;&lt;br /&gt;
** Luau includes a &#039;&#039;bit32&#039;&#039; library for bitwise operations, enabling more efficient data manipulation.&lt;br /&gt;
** Refer to the [https://luau.org/library#bit32-library bit32 library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Standard Library:&#039;&#039;&#039;&lt;br /&gt;
** Luau comes equipped with a standard library of functions designed to manipulate built-in data types.&lt;br /&gt;
** Explore the [https://luau.org/library Luau Standard Library] for a comprehensive list of available functions.&lt;br /&gt;
&lt;br /&gt;
== Feedback and Support ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== Example Scripts ==&lt;br /&gt;
&lt;br /&gt;
To help you get started, we&#039;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&#039;s desire!&lt;br /&gt;
&lt;br /&gt;
=== dialog.lua ===&lt;br /&gt;
This script demonstrates how one can interact with dialog menus.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Define the menu buttons and dialog message.&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local ToucherID = nil&lt;br /&gt;
local dialogChannel = nil&lt;br /&gt;
local listenHandle = nil&lt;br /&gt;
&lt;br /&gt;
-- This function is called when the script first starts.&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Get the object&#039;s key and compute a dialog channel number.&lt;br /&gt;
    local key = ll.GetKey()&lt;br /&gt;
    -- Extract the last 7 characters of the key and convert it from hex.&lt;br /&gt;
    dialogChannel = -1 - tonumber(string.sub(tostring(key), -7, -1), 16)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    ToucherID = ll.DetectedKey(0)&lt;br /&gt;
    -- If there is already a listen handle, then remove it&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
    end&lt;br /&gt;
    listenHandle = ll.Listen(dialogChannel, &amp;quot;&amp;quot;, ToucherID, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
    -- Set a 60-second timer for response.&lt;br /&gt;
    ll.SetTimerEvent(60.0)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when a dialog response is received.&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    if message == &amp;quot;-&amp;quot; then&lt;br /&gt;
        -- Redisplay the dialog if the &amp;quot;-&amp;quot; option is selected.&lt;br /&gt;
        ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Stop the timer, and stop the listening handler.&lt;br /&gt;
    ll.ListenRemove(listenHandle)&lt;br /&gt;
    ll.SetTimerEvent(0)&lt;br /&gt;
    -- Let the user know what they selected&lt;br /&gt;
    ll.Say(0, `You selected {message}`)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the timer expires.&lt;br /&gt;
function timer()&lt;br /&gt;
    -- Stop the timer and clean up the listener.&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.SetTimerEvent(0)&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
        ll.Whisper(0, &amp;quot;Sorry. You snooze; you lose.&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dialog_coroutine.lua ===&lt;br /&gt;
This script demonstrates how one could use coroutines to handle dialog responses, with multi-user support.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Minimal EventLoop&lt;br /&gt;
-------------------------&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; eventName it’s waiting for&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status(coro) ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Internal helper: resumes a coroutine&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local ok, eventAwaited = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    if not ok then&lt;br /&gt;
        ll.OwnerSay(`Coroutine error: {eventAwaited}`)&lt;br /&gt;
        self._coros[coro] = nil&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- If still alive, &#039;eventAwaited&#039; is the next event it wants&lt;br /&gt;
    self._coros[coro] = eventAwaited&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:handle_event(eventName, ...)&lt;br /&gt;
    ll.OwnerSay(`Handling event {eventName}`)&lt;br /&gt;
    local snapshot = table.clone(self._coros)&lt;br /&gt;
    for coro, waitingFor in pairs(snapshot) do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        elseif waitingFor == eventName then&lt;br /&gt;
            ll.OwnerSay(`Dispatching event {eventName} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Coroutines use this to yield until an event&lt;br /&gt;
local function await_event(name)&lt;br /&gt;
    assert(EventLoop.running, &amp;quot;await_event called outside a coroutine!&amp;quot;)&lt;br /&gt;
    return coroutine.yield(name)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Script Logic&lt;br /&gt;
-------------------------&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- Use the chat listener to feed the event-loop&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    -- We handle all &#039;listen&#039; events via the event-loop&lt;br /&gt;
    EventLoop:handle_event(`listen_{channel}`, channel, name, sender_id, message)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the script starts&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Seed math.random so each new script run doesn’t repeat the same channels&lt;br /&gt;
    math.randomseed(ll.GetUnixTime())&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Script started with random channels for each user.&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- A coroutine function for a single user&#039;s dialog flow&lt;br /&gt;
local function handle_dialog_for_user(userId)&lt;br /&gt;
    -- Use a random channel&lt;br /&gt;
    local channel = math.random(0x1, 0xFFFF)&lt;br /&gt;
    -- Create a listener for that channel&lt;br /&gt;
    local listenHandle = ll.Listen(channel, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        -- Show the user a dialog&lt;br /&gt;
        ll.Dialog(userId, dialogInfo, buttons, channel)&lt;br /&gt;
&lt;br /&gt;
        -- Wait for the next &#039;listen&#039; event (channel, name, sender_id, message)&lt;br /&gt;
        local c, n, sid, msg = await_event(`listen_{channel}`)&lt;br /&gt;
&lt;br /&gt;
        -- If this &amp;quot;listen&amp;quot; event isn&#039;t for our channel/user, ignore it&lt;br /&gt;
        if c == channel and sid == userId then&lt;br /&gt;
            -- If user pressed &amp;quot;-&amp;quot;, re-display the menu, else they picked a final color&lt;br /&gt;
            if msg ~= &amp;quot;-&amp;quot; then&lt;br /&gt;
                ll.Say(0, `{n} selected {msg}`)&lt;br /&gt;
                -- Now that they&#039;ve chosen something else, remove the listener and finish&lt;br /&gt;
                ll.ListenRemove(listenHandle)&lt;br /&gt;
                return&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    for i=0, num_detected-1 do&lt;br /&gt;
        local toucherId = ll.DetectedKey(i)&lt;br /&gt;
        -- Create a separate coroutine for each person who touches&lt;br /&gt;
        EventLoop:create_task(function()&lt;br /&gt;
            handle_dialog_for_user(toucherId)&lt;br /&gt;
        end)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Run state_entry on load:&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== More Examples ===&lt;br /&gt;
&lt;br /&gt;
Find more example scripts at [[Luau Example Scripts]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Luau_Example_Scripts&amp;diff=1218015</id>
		<title>Luau Example Scripts</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Luau_Example_Scripts&amp;diff=1218015"/>
		<updated>2025-03-04T23:04:06Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Signal Linden moved page Luau Example Scripts to Luau Examples&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Luau Examples]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Luau_Examples&amp;diff=1218014</id>
		<title>Luau Examples</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Luau_Examples&amp;diff=1218014"/>
		<updated>2025-03-04T23:04:05Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Signal Linden moved page Luau Example Scripts to Luau Examples&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Luau Examples =&lt;br /&gt;
&lt;br /&gt;
Example [[Luau Alpha|Luau]] scripts&lt;br /&gt;
&lt;br /&gt;
== vehicle.lua ==&lt;br /&gt;
This script demonstrates how one may create their own vehicle scripts, which don’t rely on the underlying LL vehicle system. &lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Control and permission constants&lt;br /&gt;
local PERMISSION_TAKE_CONTROLS = 0x4&lt;br /&gt;
local CONTROL_FWD       = 0x1&lt;br /&gt;
local CONTROL_BACK      = 0x2&lt;br /&gt;
local CONTROL_UP        = 0x10&lt;br /&gt;
local CONTROL_DOWN      = 0x20&lt;br /&gt;
local CONTROL_LEFT      = 0x4&lt;br /&gt;
local CONTROL_RIGHT     = 0x8&lt;br /&gt;
local PRIM_ROT_LOCAL    = integer(29)&lt;br /&gt;
&lt;br /&gt;
-- These two will hold the link numbers for left and right front wheels&lt;br /&gt;
local LINK_WHEEL_LEFT = 0&lt;br /&gt;
local LINK_WHEEL_RIGHT = 0&lt;br /&gt;
&lt;br /&gt;
-- Integra Type R Engine properties&lt;br /&gt;
local Engine = {&lt;br /&gt;
    rpm = 800,              -- current engine RPM (starts at idle)&lt;br /&gt;
    gear = 0,               -- 0 = neutral; -1 = reverse; 1-5 for forward gears&lt;br /&gt;
    idleRPM = 800,          -- idle RPM&lt;br /&gt;
    maxRPM = 8300,          -- redline&lt;br /&gt;
    throttle = 0.0,         -- throttle value (0.0 to 1.0)&lt;br /&gt;
    gearRatios = {          -- Integra Type R gear ratios&lt;br /&gt;
        [-1] = 3.727,       -- reverse gear ratio&lt;br /&gt;
        [0]  = 0,          -- neutral&lt;br /&gt;
        [1]  = 3.727,&lt;br /&gt;
        [2]  = 2.256,&lt;br /&gt;
        [3]  = 1.729,&lt;br /&gt;
        [4]  = 1.416,&lt;br /&gt;
        [5]  = 1.194&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FINAL_DRIVE = 4.1   -- Final drive ratio&lt;br /&gt;
local WHEEL_RADIUS = 0.3  -- in meters&lt;br /&gt;
&lt;br /&gt;
-- Vehicle properties (using realistic Integra Type R mass)&lt;br /&gt;
local Vehicle = {&lt;br /&gt;
    speed = 0,      -- speed in m/s; negative indicates reverse motion&lt;br /&gt;
    mass = 1200,    -- mass in kg&lt;br /&gt;
    angle = 0,      -- heading angle in radians&lt;br /&gt;
    steering = 0,   -- steering input: -1 (left), 0 (none), 1 (right)&lt;br /&gt;
    brake = false   -- flag indicating if brakes are applied&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Engine constants (using realistic Integra Type R values)&lt;br /&gt;
local MAX_TORQUE = 200   -- in Nm&lt;br /&gt;
&lt;br /&gt;
-- Brake force constant (for active braking)&lt;br /&gt;
local BRAKE_FORCE = 4000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Define a torque curve function:&lt;br /&gt;
local function torqueCurve(rpm)&lt;br /&gt;
    local peakRPM = 6000&lt;br /&gt;
    if rpm &amp;lt; peakRPM then&lt;br /&gt;
        return 0.8 + 0.2 * ((rpm - Engine.idleRPM) / (peakRPM - Engine.idleRPM))&lt;br /&gt;
    else&lt;br /&gt;
        local factor = 1 - 0.5 * ((rpm - peakRPM) / (Engine.maxRPM - peakRPM))&lt;br /&gt;
        return math.max(0.5, factor)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Engine braking force when throttle is zero in gear.&lt;br /&gt;
local ENGINE_BRAKE_FORCE = 2000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Friction and drag constants&lt;br /&gt;
local G = 9.81            -- gravity (m/s^2)&lt;br /&gt;
local C_rr_inGear = 0.015 -- rolling resistance coefficient in gear&lt;br /&gt;
local C_rr_neutral = 0.05 -- rolling resistance coefficient in neutral&lt;br /&gt;
&lt;br /&gt;
local airDensity = 1.225      -- kg/m^3&lt;br /&gt;
local dragCoefficient = 0.3   -- aerodynamic drag coefficient&lt;br /&gt;
local frontalArea = 2.2       -- in m^2&lt;br /&gt;
&lt;br /&gt;
local function getAerodynamicDrag(speed)&lt;br /&gt;
    return 0.5 * airDensity * dragCoefficient * frontalArea * speed * speed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Compute engine RPM from vehicle speed when in gear.&lt;br /&gt;
local function computeEngineRPMFromSpeed()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return Engine.rpm  -- In neutral, RPM is managed independently.&lt;br /&gt;
    else&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local rpm = math.abs(Vehicle.speed) * conversionFactor&lt;br /&gt;
        if rpm &amp;lt; Engine.idleRPM then rpm = Engine.idleRPM end&lt;br /&gt;
        return rpm&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- For neutral, update RPM toward a target based on throttle.&lt;br /&gt;
local function updateEngineRPMNeutral()&lt;br /&gt;
    local targetRPM = Engine.idleRPM + (Engine.maxRPM - Engine.idleRPM) * Engine.throttle&lt;br /&gt;
    local changeRate = 200  -- RPM per tick&lt;br /&gt;
    if Engine.rpm &amp;lt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.min(Engine.rpm + changeRate, targetRPM)&lt;br /&gt;
    elseif Engine.rpm &amp;gt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.max(Engine.rpm - changeRate, targetRPM)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Calculate the engine force delivered to the wheels.&lt;br /&gt;
local function getEngineForce()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return 0  -- In neutral, no drive force is transmitted.&lt;br /&gt;
    else&lt;br /&gt;
        if Engine.throttle == 0 then&lt;br /&gt;
            -- Apply engine braking when throttle is released.&lt;br /&gt;
            return -ENGINE_BRAKE_FORCE&lt;br /&gt;
        else&lt;br /&gt;
            local torque = Engine.throttle * MAX_TORQUE * torqueCurve(Engine.rpm)&lt;br /&gt;
            local force = (torque * Engine.gearRatios[Engine.gear] * FINAL_DRIVE) / WHEEL_RADIUS&lt;br /&gt;
            return force&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Starts the engine by setting it to idle RPM.&lt;br /&gt;
local function startEngine()&lt;br /&gt;
    Engine.rpm = Engine.idleRPM&lt;br /&gt;
    ll.Say(0, &amp;quot;Engine started. RPM: &amp;quot; .. Engine.rpm)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Shifts the gear if valid.&lt;br /&gt;
local function shiftGear(newGear)&lt;br /&gt;
    if Engine.gearRatios[newGear] then&lt;br /&gt;
        Engine.gear = newGear&lt;br /&gt;
        ll.Say(0, &amp;quot;Shifted to gear &amp;quot; .. newGear)&lt;br /&gt;
    else&lt;br /&gt;
        ll.Say(0, &amp;quot;Invalid gear: &amp;quot; .. newGear)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function findWheels()&lt;br /&gt;
    local total = ll.GetNumberOfPrims()&lt;br /&gt;
    for i = 1, total do&lt;br /&gt;
        local name = ll.GetLinkName(i)&lt;br /&gt;
        if name == &amp;quot;WHEEL_LF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_LEFT = i&lt;br /&gt;
        elseif name == &amp;quot;WHEEL_RF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_RIGHT = i&lt;br /&gt;
        end&lt;br /&gt;
    end &lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local castRayCounter = 0&lt;br /&gt;
local lastGrounded = false&lt;br /&gt;
&lt;br /&gt;
local function isVehicleOnGround()&lt;br /&gt;
    castRayCounter = castRayCounter + 1&lt;br /&gt;
    if castRayCounter &amp;gt;= 5 then&lt;br /&gt;
        castRayCounter = 0&lt;br /&gt;
        local pos = ll.GetPos()&lt;br /&gt;
        local rayDistance = 4.5  -- threshold distance (in meters) to consider as &amp;quot;grounded&amp;quot;&lt;br /&gt;
        local rayStart = pos&lt;br /&gt;
        local rayEnd = pos + vector(0, 0, -rayDistance)&lt;br /&gt;
        local hitResult = ll.CastRay(rayStart, rayEnd, {})&lt;br /&gt;
        if table.getn(hitResult) == 3 then&lt;br /&gt;
            lastGrounded = true&lt;br /&gt;
        else&lt;br /&gt;
            lastGrounded = false&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return lastGrounded&lt;br /&gt;
end &lt;br /&gt;
&lt;br /&gt;
-- Timer event: updates engine, vehicle physics, steering, and rotates the wheels based on steering.&lt;br /&gt;
function timer()&lt;br /&gt;
    local dt = 0.1  -- time step in seconds&lt;br /&gt;
&lt;br /&gt;
    -- For neutral, update RPM based on throttle.&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        updateEngineRPMNeutral()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Calculate forces.&lt;br /&gt;
    local C_rr = (Engine.gear == 0) and C_rr_neutral or C_rr_inGear&lt;br /&gt;
    local F_roll = Vehicle.mass * G * C_rr&lt;br /&gt;
    local engineForce = getEngineForce()&lt;br /&gt;
    local dragForce = getAerodynamicDrag(math.abs(Vehicle.speed))&lt;br /&gt;
    local netForce = engineForce - (F_roll + dragForce)&lt;br /&gt;
&lt;br /&gt;
    -- Apply braking force if brakes are engaged.&lt;br /&gt;
    if Vehicle.brake then&lt;br /&gt;
        netForce = netForce - BRAKE_FORCE&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local acceleration = netForce / Vehicle.mass&lt;br /&gt;
    local vel = ll.GetVel()&lt;br /&gt;
    local horizontal_vec = vector(vel.x, vel.y, 0) &lt;br /&gt;
    Vehicle.speed = (ll.VecMag(horizontal_vec) + acceleration * dt)&lt;br /&gt;
&lt;br /&gt;
    -- Limit vehicle speed based on gear.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        if Vehicle.speed &amp;lt; 0 then &lt;br /&gt;
            Vehicle.speed = 0 &lt;br /&gt;
        end&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local maxSpeed = Engine.maxRPM / conversionFactor&lt;br /&gt;
        if Vehicle.speed &amp;gt; maxSpeed then&lt;br /&gt;
            Vehicle.speed = maxSpeed&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- When in gear, update RPM based on the new speed.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        Engine.rpm = computeEngineRPMFromSpeed()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Steering update using angular velocity and quaternions:&lt;br /&gt;
    local STEERING_RATE = math.rad(6)  -- desired steering rate per tick (radians)&lt;br /&gt;
    local yawDelta = Vehicle.steering * STEERING_RATE  -- small rotation for this tick&lt;br /&gt;
&lt;br /&gt;
    if yawDelta ~= 0 then&lt;br /&gt;
        local deltaQuat = ll.Euler2Rot(vector(0, 0, yawDelta))&lt;br /&gt;
        local angularSpeed = yawDelta / dt&lt;br /&gt;
        local angularVelocity = vector(0, 0, angularSpeed)&lt;br /&gt;
        ll.SetAngularVelocity(angularVelocity, 1)&lt;br /&gt;
        Vehicle.angle = Vehicle.angle + yawDelta&lt;br /&gt;
    else&lt;br /&gt;
        ll.SetAngularVelocity(vector(0, 0, 0), 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Display engine RPM and speed (converted to mph).&lt;br /&gt;
    local speed_mph = math.abs(Vehicle.speed) * 2.23694&lt;br /&gt;
    ll.SetText(&amp;quot;RPM: &amp;quot; .. math.floor(Engine.rpm) ..&lt;br /&gt;
               &amp;quot; | Speed: &amp;quot; .. string.format(&amp;quot;%.1f&amp;quot;, speed_mph) ..&lt;br /&gt;
               &amp;quot; mph&amp;quot;, vector(0,0,0), 1)&lt;br /&gt;
&lt;br /&gt;
    -- Only update velocity if the vehicle is near the ground, and we&#039;re in gear.&lt;br /&gt;
    if isVehicleOnGround() and Engine.gear ~= 0 then&lt;br /&gt;
        local velocityVector&lt;br /&gt;
        if Engine.gear &amp;gt; 0 then&lt;br /&gt;
            velocityVector = vector(Vehicle.speed, 0, 0)&lt;br /&gt;
        else&lt;br /&gt;
            -- Reverse gear&lt;br /&gt;
            velocityVector = vector(-Vehicle.speed, 0, 0)&lt;br /&gt;
        end&lt;br /&gt;
        ll.SetVelocity(velocityVector, 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Update the wheel rotations to visually match the steering.&lt;br /&gt;
    local maxWheelTurn = math.rad(30)  -- maximum wheel turn angle in radians&lt;br /&gt;
    local wheelAngle = -Vehicle.steering * maxWheelTurn&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_LEFT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_LEFT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(-4.71239, -wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_RIGHT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_RIGHT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(4.71239, wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched: starts engine and simulation.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    ll.Say(0, &amp;quot;Touch detected, requesting controls and starting engine.&amp;quot;)&lt;br /&gt;
    ll.RequestPermissions(ll.GetOwner(), PERMISSION_TAKE_CONTROLS)&lt;br /&gt;
    startEngine()&lt;br /&gt;
    findWheels()&lt;br /&gt;
    ll.SetTimerEvent(0.1)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when runtime permissions are granted.&lt;br /&gt;
function run_time_permissions(perm)&lt;br /&gt;
    if bit32.band(perm, PERMISSION_TAKE_CONTROLS) then&lt;br /&gt;
        ll.Say(0, &amp;quot;Permissions granted.&amp;quot;)&lt;br /&gt;
        local controlMask = bit32.bor(CONTROL_FWD, CONTROL_BACK, CONTROL_UP, CONTROL_DOWN, CONTROL_LEFT, CONTROL_RIGHT)&lt;br /&gt;
        ll.TakeControls(controlMask, 1, 1)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Handle control events:&lt;br /&gt;
-- • CONTROL_FWD applies throttle.&lt;br /&gt;
-- • CONTROL_BACK applies the brakes.&lt;br /&gt;
-- • CONTROL_UP and CONTROL_DOWN shift gears.&lt;br /&gt;
-- • CONTROL_LEFT and CONTROL_RIGHT steer the vehicle.&lt;br /&gt;
function control(avatar_id, level, edge)&lt;br /&gt;
    local start = bit32.band(level, edge)            &lt;br /&gt;
    local finish = bit32.band(bit32.bnot(level), edge) &lt;br /&gt;
    local held = bit32.band(level, bit32.bnot(edge))   &lt;br /&gt;
    local untouched = bit32.bnot(bit32.bor(level, edge)) &lt;br /&gt;
&lt;br /&gt;
    if bit32.band(held, CONTROL_BACK) ~= 0 then&lt;br /&gt;
        Vehicle.brake = true&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    elseif bit32.band(held, CONTROL_FWD) ~= 0 then&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 1.0&lt;br /&gt;
        if Engine.gear == 0 then&lt;br /&gt;
            updateEngineRPMNeutral()&lt;br /&gt;
        end&lt;br /&gt;
    else&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_UP) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear + 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_DOWN) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear - 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local steeringInput = 0&lt;br /&gt;
    if bit32.band(held, CONTROL_LEFT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput + 1&lt;br /&gt;
    end&lt;br /&gt;
    if bit32.band(held, CONTROL_RIGHT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput - 1&lt;br /&gt;
    end&lt;br /&gt;
    Vehicle.steering = steeringInput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== weather_box.lua ==&lt;br /&gt;
This script demonstrates coroutines, as well as working with a JSON-based web API:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local TEXTBOX_CHANNEL = 323242&lt;br /&gt;
-- kludge because we don&#039;t have LSL constants in here yet&lt;br /&gt;
local HTTP_BODY_MAXLENGTH = integer(2)&lt;br /&gt;
local PRIM_TEXT = integer(26)&lt;br /&gt;
local PRIM_SIZE = integer(7)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
gCityName = &amp;quot;&amp;quot;&lt;br /&gt;
gLatLong = nil  -- A table of 2 elems when it&#039;s populated&lt;br /&gt;
gCityRequestTask = nil&lt;br /&gt;
gWeatherRequestTask = nil&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- JSON stuff&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- From https://github.com/rxi/json.lua/blob/master/json.lua&lt;br /&gt;
&lt;br /&gt;
local json = {}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map = {&lt;br /&gt;
  [ &amp;quot;\\&amp;quot; ] = &amp;quot;\\&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\&amp;quot;&amp;quot; ] = &amp;quot;\&amp;quot;&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\b&amp;quot; ] = &amp;quot;b&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\f&amp;quot; ] = &amp;quot;f&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\n&amp;quot; ] = &amp;quot;n&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\r&amp;quot; ] = &amp;quot;r&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\t&amp;quot; ] = &amp;quot;t&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map_inv = { [ &amp;quot;/&amp;quot; ] = &amp;quot;/&amp;quot; }&lt;br /&gt;
for k, v in pairs(escape_char_map) do&lt;br /&gt;
  escape_char_map_inv[v] = k&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local parse&lt;br /&gt;
&lt;br /&gt;
local function create_set(...)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  for i = 1, select(&amp;quot;#&amp;quot;, ...) do&lt;br /&gt;
    res[ select(i, ...) ] = true&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local space_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;)&lt;br /&gt;
local delim_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, &amp;quot;]&amp;quot;, &amp;quot;}&amp;quot;, &amp;quot;,&amp;quot;)&lt;br /&gt;
local escape_chars  = create_set(&amp;quot;\\&amp;quot;, &amp;quot;/&amp;quot;, &#039;&amp;quot;&#039;, &amp;quot;b&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;r&amp;quot;, &amp;quot;t&amp;quot;, &amp;quot;u&amp;quot;)&lt;br /&gt;
local literals      = create_set(&amp;quot;true&amp;quot;, &amp;quot;false&amp;quot;, &amp;quot;null&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local literal_map = {&lt;br /&gt;
  [ &amp;quot;true&amp;quot;  ] = true,&lt;br /&gt;
  [ &amp;quot;false&amp;quot; ] = false,&lt;br /&gt;
  [ &amp;quot;null&amp;quot;  ] = nil,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function next_char(str, idx, set, negate)&lt;br /&gt;
  for i = idx, #str do&lt;br /&gt;
    if set[str:sub(i, i)] ~= negate then&lt;br /&gt;
      return i&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  return #str + 1&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function decode_error(str, idx, msg)&lt;br /&gt;
  local line_count = 1&lt;br /&gt;
  local col_count = 1&lt;br /&gt;
  for i = 1, idx - 1 do&lt;br /&gt;
    col_count = col_count + 1&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;\n&amp;quot; then&lt;br /&gt;
      line_count = line_count + 1&lt;br /&gt;
      col_count = 1&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;%s at line %d col %d&amp;quot;, msg, line_count, col_count) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function codepoint_to_utf8(n)&lt;br /&gt;
  -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&amp;amp;id=iws-appendixa&lt;br /&gt;
  local f = math.floor&lt;br /&gt;
  if n &amp;lt;= 0x7f then&lt;br /&gt;
    return string.char(n)&lt;br /&gt;
  elseif n &amp;lt;= 0x7ff then&lt;br /&gt;
    return string.char(f(n / 64) + 192, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0xffff then&lt;br /&gt;
    return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0x10ffff then&lt;br /&gt;
    return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,&lt;br /&gt;
                       f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;invalid unicode codepoint &#039;%x&#039;&amp;quot;, n) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_unicode_escape(s)&lt;br /&gt;
  local n1 = tonumber( s:sub(1, 4),  16 )&lt;br /&gt;
  local n2 = tonumber( s:sub(7, 10), 16 )&lt;br /&gt;
   -- Surrogate pair?&lt;br /&gt;
  if n2 then&lt;br /&gt;
    return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)&lt;br /&gt;
  else&lt;br /&gt;
    return codepoint_to_utf8(n1)&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_string(str, i)&lt;br /&gt;
  local res = &amp;quot;&amp;quot;&lt;br /&gt;
  local j = i + 1&lt;br /&gt;
  local k = j&lt;br /&gt;
&lt;br /&gt;
  while j &amp;lt;= #str do&lt;br /&gt;
    local x = str:byte(j)&lt;br /&gt;
&lt;br /&gt;
    if x &amp;lt; 32 then&lt;br /&gt;
      decode_error(str, j, &amp;quot;control character in string&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    elseif x == 92 then -- `\`: Escape&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      j = j + 1&lt;br /&gt;
      local c = str:sub(j, j)&lt;br /&gt;
      if c == &amp;quot;u&amp;quot; then&lt;br /&gt;
        local hex = str:match(&amp;quot;^[dD][89aAbB]%x%x\\u%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or str:match(&amp;quot;^%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or decode_error(str, j - 1, &amp;quot;invalid unicode escape in string&amp;quot;)&lt;br /&gt;
        res = res .. parse_unicode_escape(hex)&lt;br /&gt;
        j = j + #hex&lt;br /&gt;
      else&lt;br /&gt;
        if not escape_chars[c] then&lt;br /&gt;
          decode_error(str, j - 1, &amp;quot;invalid escape char &#039;&amp;quot; .. c .. &amp;quot;&#039; in string&amp;quot;)&lt;br /&gt;
        end&lt;br /&gt;
        res = res .. escape_char_map_inv[c]&lt;br /&gt;
      end&lt;br /&gt;
      k = j + 1&lt;br /&gt;
&lt;br /&gt;
    elseif x == 34 then -- `&amp;quot;`: End of string&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      return res, j + 1&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    j = j + 1&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  decode_error(str, i, &amp;quot;expected closing quote for string&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_number(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local s = str:sub(i, x - 1)&lt;br /&gt;
  local n = tonumber(s)&lt;br /&gt;
  if not n then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid number &#039;&amp;quot; .. s .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return n, x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_literal(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local word = str:sub(i, x - 1)&lt;br /&gt;
  if not literals[word] then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid literal &#039;&amp;quot; .. word .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return literal_map[word], x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_array(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  local n = 1&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local x&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of array?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;]&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read token&lt;br /&gt;
    x, i = parse(str, i)&lt;br /&gt;
    res[n] = x&lt;br /&gt;
    n = n + 1&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;]&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;]&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_object(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local key, val&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of object?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;}&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read key&lt;br /&gt;
    if str:sub(i, i) ~= &#039;&amp;quot;&#039; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected string for key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    key, i = parse(str, i)&lt;br /&gt;
    -- Read &#039;:&#039; delimiter&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    if str:sub(i, i) ~= &amp;quot;:&amp;quot; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected &#039;:&#039; after key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    i = next_char(str, i + 1, space_chars, true)&lt;br /&gt;
    -- Read value&lt;br /&gt;
    val, i = parse(str, i)&lt;br /&gt;
    -- Set&lt;br /&gt;
    res[key] = val&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;}&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;}&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local char_func_map = {&lt;br /&gt;
  [ &#039;&amp;quot;&#039; ] = parse_string,&lt;br /&gt;
  [ &amp;quot;0&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;1&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;2&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;3&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;4&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;5&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;6&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;7&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;8&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;9&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;-&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;t&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;f&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;n&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;[&amp;quot; ] = parse_array,&lt;br /&gt;
  [ &amp;quot;{&amp;quot; ] = parse_object,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
parse = function(str, idx)&lt;br /&gt;
  local chr = str:sub(idx, idx)&lt;br /&gt;
  local f = char_func_map[chr]&lt;br /&gt;
  if f then&lt;br /&gt;
    return f(str, idx)&lt;br /&gt;
  end&lt;br /&gt;
  decode_error(str, idx, &amp;quot;unexpected character &#039;&amp;quot; .. chr .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function json.decode(str)&lt;br /&gt;
  if type(str) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
    error(&amp;quot;expected argument of type string, got &amp;quot; .. type(str))&lt;br /&gt;
  end&lt;br /&gt;
  local res, idx = parse(str, next_char(str, 1, space_chars, true))&lt;br /&gt;
  idx = next_char(str, idx, space_chars, true)&lt;br /&gt;
  if idx &amp;lt;= #str then&lt;br /&gt;
    decode_error(str, idx, &amp;quot;trailing garbage&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Loop&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- A naive little event loop that lets you track running coroutines and dispatches&lt;br /&gt;
-- the events they&#039;re waiting for to them.&lt;br /&gt;
&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; awaited event&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    -- false has no semantic meaning here, we just want to reserve the space for&lt;br /&gt;
    -- the coroutine in the table without saying we&#039;re awaiting anything yet.&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- (internal) run the event&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    assert(coroutine.status(coro) ~= &amp;quot;dead&amp;quot;)&lt;br /&gt;
    -- assert(not self.running)&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    -- Since we&#039;re running, this isn&#039;t currently awaiting anything&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local success, ret = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    -- If we&#039;re not done then ret should be the kind of event we&#039;re awaiting.&lt;br /&gt;
    -- Otherwise it&#039;s an error message or something. Who cares.&lt;br /&gt;
    if not success then&lt;br /&gt;
        -- Get rid of the coro&lt;br /&gt;
        if ret then&lt;br /&gt;
            ll.OwnerSay(`Might have an error: {ret}`)&lt;br /&gt;
        end&lt;br /&gt;
        ret = nil&lt;br /&gt;
    end&lt;br /&gt;
    self._coros[coro] = ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Wake up any tasks that are waiting for this kind of event&lt;br /&gt;
function EventLoop:handle_event(name, ...)&lt;br /&gt;
    -- We may mutate self._coros, so do a clone&lt;br /&gt;
    local coros = table.clone(self._coros)&lt;br /&gt;
    ll.OwnerSay(`Handling {name}`)&lt;br /&gt;
&lt;br /&gt;
    for coro, awaited_event in coros do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            -- Hmm, this coroutine is dead, prune it.&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if awaited_event == name then&lt;br /&gt;
            ll.OwnerSay(`Dispatching {name} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_event_catcher(name)&lt;br /&gt;
    local function catcher(...)&lt;br /&gt;
        ll.OwnerSay(&amp;quot;yay&amp;quot;)&lt;br /&gt;
        EventLoop:handle_event(name, ...)&lt;br /&gt;
    end&lt;br /&gt;
    return catcher&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Helper function for waiting and telling the EventLoop what event you want&lt;br /&gt;
local function await_event(kind)&lt;br /&gt;
    -- You had better do this inside a running event loop&lt;br /&gt;
    assert(EventLoop.running)&lt;br /&gt;
    -- This will yield, the EventLoop will resume us once it has&lt;br /&gt;
    -- an event we might be interested in.&lt;br /&gt;
    return coroutine.yield(kind)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Script-specific functions&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
local function hide_children()&lt;br /&gt;
    for i=2,ll.GetNumberOfPrims() do&lt;br /&gt;
        -- Hide all the child prims&lt;br /&gt;
        ll.SetLinkAlpha(i, 0, -1)&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(i, {&lt;br /&gt;
            PRIM_SIZE, vector(0.5, 0.5, 0.5),&lt;br /&gt;
            PRIM_TEXT, &amp;quot;&amp;quot;, vector(1,1,1), 1&lt;br /&gt;
        })&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_textbox(avatar_id, message, channel)&lt;br /&gt;
    ll.TextBox(avatar_id, message, channel)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        local resp_channel, name, id, resp = await_event(&amp;quot;listen&amp;quot;)&lt;br /&gt;
        if id ~= avatar_id or channel ~= resp_channel then&lt;br /&gt;
            -- If this isn&#039;t a message we&#039;re interested in then wait for the next event&lt;br /&gt;
            ll.OwnerSay(`{id}, {avatar_id}, {resp_channel}, {channel}`)&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return resp&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function make_json_request(url)&lt;br /&gt;
    local req_id = ll.HTTPRequest(url, {HTTP_BODY_MAXLENGTH, integer(16000)}, &amp;quot;&amp;quot;)&lt;br /&gt;
    while true do&lt;br /&gt;
        local ev_id, status, metadata, body = await_event(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
        if ev_id ~= req_id then&lt;br /&gt;
            -- Not a response for the HTTP request we were waiting on, wait for the next event.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return status, metadata, body&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function request_weather()&lt;br /&gt;
    -- And you can use a wrapper like this to make an async function callable within both normal and coroutine contexts.&lt;br /&gt;
    -- Normally this sort of thing would not be necessary, but here it is since the EventLoop lives in our own code,&lt;br /&gt;
    -- and isn&#039;t a property of the actual script engine.&lt;br /&gt;
    if gWeatherRequestTask then&lt;br /&gt;
        EventLoop:kill_task(gWeatherRequestTask)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    gWeatherRequestTask = EventLoop:create_task(function()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Requesting weather&amp;quot;)&lt;br /&gt;
        local status, metadata, body = make_json_request(`https://api.open-meteo.com/v1/forecast?latitude={gLatLong[1]}&amp;amp;longitude={gLatLong[2]}&amp;amp;current=temperature_2m&amp;amp;daily=temperature_2m_max`)&lt;br /&gt;
        if status ~= 200 then&lt;br /&gt;
            ll.OwnerSay(&amp;quot;Error requesting weather&amp;quot;)&lt;br /&gt;
            return&lt;br /&gt;
        end&lt;br /&gt;
        local parsed = json.decode(body)&lt;br /&gt;
&lt;br /&gt;
        local current = parsed[&amp;quot;current&amp;quot;]&lt;br /&gt;
        ll.OwnerSay(`Temperature is {current[&amp;quot;temperature_2m&amp;quot;]}C`)&lt;br /&gt;
        ll.SetText(`Temperature in {gCityName} is currently {current[&amp;quot;temperature_2m&amp;quot;]}C`, vector(1,1,1), 1)&lt;br /&gt;
&lt;br /&gt;
        -- Show the highs for each day&lt;br /&gt;
        local days = parsed[&amp;quot;daily&amp;quot;][&amp;quot;temperature_2m_max&amp;quot;]&lt;br /&gt;
        for i=1,#days do&lt;br /&gt;
            local link_num = i + 1&lt;br /&gt;
            -- Higher temps = bigger on Z, negatives are unrepresentable :)&lt;br /&gt;
            local z_size = (days[i] / 30) * 2&lt;br /&gt;
            ll.SetLinkAlpha(link_num, 1, -1)&lt;br /&gt;
            ll.SetLinkPrimitiveParamsFast(link_num, {&lt;br /&gt;
                PRIM_SIZE, vector(0.5, 0.5, z_size),&lt;br /&gt;
                PRIM_TEXT, `{days[i]}C`, vector(1,1,1), 1&lt;br /&gt;
            })&lt;br /&gt;
        end&lt;br /&gt;
    end)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ask_for_city()&lt;br /&gt;
    local resp = send_textbox(ll.GetOwner(), &amp;quot;What city do you want the weather for?&amp;quot;, TEXTBOX_CHANNEL)&lt;br /&gt;
    if not resp then&lt;br /&gt;
        ll.OwnerSay(&amp;quot;Got no response for city request.&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    ll.OwnerSay(`You want the weather for {resp}.`)&lt;br /&gt;
    gLatLong = nil&lt;br /&gt;
    gCityName = resp&lt;br /&gt;
&lt;br /&gt;
    hide_children()&lt;br /&gt;
&lt;br /&gt;
    -- request the lat and long for this city&lt;br /&gt;
    local status, metadata, body = make_json_request(`https://nominatim.openstreetmap.org/search.php?city={ll.EscapeURL(resp)}&amp;amp;format=jsonv2`)&lt;br /&gt;
    local parsed = json.decode(body)&lt;br /&gt;
    if not #parsed then&lt;br /&gt;
        ll.OwnerSay(`Got no results for {resp}!`)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local first_result = parsed[1]&lt;br /&gt;
    gLatLong = {first_result[&amp;quot;lat&amp;quot;], first_result[&amp;quot;lon&amp;quot;]}&lt;br /&gt;
    ll.OwnerSay(`City Lat: {gLatLong[1]}, Long: {gLatLong[2]}`)&lt;br /&gt;
&lt;br /&gt;
    request_weather()&lt;br /&gt;
&lt;br /&gt;
    -- request weather again in 3 mins&lt;br /&gt;
    ll.SetTimerEvent(180)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Handlers&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- If we don&#039;t care about conventionally dispatched events, we can just set the event handler to directly send&lt;br /&gt;
-- event data to the event loop.&lt;br /&gt;
http_response = EventLoop:create_event_catcher(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
listen = EventLoop:create_event_catcher(&amp;quot;listen&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- More conventional event handlers work too.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    for i=0,num-1 do&lt;br /&gt;
        if ll.GetOwner() ~= ll.DetectedKey(i) then&lt;br /&gt;
            -- Go to next loop if it wasn&#039;t the owner touching us.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if gCityRequestTask then&lt;br /&gt;
            EventLoop:kill_task(gCityRequestTask)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Spin up a task to ask the user for their city&lt;br /&gt;
        gCityRequestTask = EventLoop:create_task(ask_for_city)&lt;br /&gt;
        break&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function timer()&lt;br /&gt;
    request_weather()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Not strictly necessary, but I like having the main function have its own scope.&lt;br /&gt;
-- Rather than just have top-level logic.&lt;br /&gt;
local function main()&lt;br /&gt;
    ll.Listen(TEXTBOX_CHANNEL, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.SetText(&amp;quot;&amp;quot;, vector(1,1,1), 1)&lt;br /&gt;
    hide_children()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Weatherbox operational!&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
main()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Luau_Examples&amp;diff=1218013</id>
		<title>Luau Examples</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Luau_Examples&amp;diff=1218013"/>
		<updated>2025-03-04T23:03:31Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Created page with &amp;quot;= Luau Examples =  Example Luau scripts  == vehicle.lua == This script demonstrates how one may create their own vehicle scripts, which don’t rely on the unde...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Luau Examples =&lt;br /&gt;
&lt;br /&gt;
Example [[Luau Alpha|Luau]] scripts&lt;br /&gt;
&lt;br /&gt;
== vehicle.lua ==&lt;br /&gt;
This script demonstrates how one may create their own vehicle scripts, which don’t rely on the underlying LL vehicle system. &lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Control and permission constants&lt;br /&gt;
local PERMISSION_TAKE_CONTROLS = 0x4&lt;br /&gt;
local CONTROL_FWD       = 0x1&lt;br /&gt;
local CONTROL_BACK      = 0x2&lt;br /&gt;
local CONTROL_UP        = 0x10&lt;br /&gt;
local CONTROL_DOWN      = 0x20&lt;br /&gt;
local CONTROL_LEFT      = 0x4&lt;br /&gt;
local CONTROL_RIGHT     = 0x8&lt;br /&gt;
local PRIM_ROT_LOCAL    = integer(29)&lt;br /&gt;
&lt;br /&gt;
-- These two will hold the link numbers for left and right front wheels&lt;br /&gt;
local LINK_WHEEL_LEFT = 0&lt;br /&gt;
local LINK_WHEEL_RIGHT = 0&lt;br /&gt;
&lt;br /&gt;
-- Integra Type R Engine properties&lt;br /&gt;
local Engine = {&lt;br /&gt;
    rpm = 800,              -- current engine RPM (starts at idle)&lt;br /&gt;
    gear = 0,               -- 0 = neutral; -1 = reverse; 1-5 for forward gears&lt;br /&gt;
    idleRPM = 800,          -- idle RPM&lt;br /&gt;
    maxRPM = 8300,          -- redline&lt;br /&gt;
    throttle = 0.0,         -- throttle value (0.0 to 1.0)&lt;br /&gt;
    gearRatios = {          -- Integra Type R gear ratios&lt;br /&gt;
        [-1] = 3.727,       -- reverse gear ratio&lt;br /&gt;
        [0]  = 0,          -- neutral&lt;br /&gt;
        [1]  = 3.727,&lt;br /&gt;
        [2]  = 2.256,&lt;br /&gt;
        [3]  = 1.729,&lt;br /&gt;
        [4]  = 1.416,&lt;br /&gt;
        [5]  = 1.194&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FINAL_DRIVE = 4.1   -- Final drive ratio&lt;br /&gt;
local WHEEL_RADIUS = 0.3  -- in meters&lt;br /&gt;
&lt;br /&gt;
-- Vehicle properties (using realistic Integra Type R mass)&lt;br /&gt;
local Vehicle = {&lt;br /&gt;
    speed = 0,      -- speed in m/s; negative indicates reverse motion&lt;br /&gt;
    mass = 1200,    -- mass in kg&lt;br /&gt;
    angle = 0,      -- heading angle in radians&lt;br /&gt;
    steering = 0,   -- steering input: -1 (left), 0 (none), 1 (right)&lt;br /&gt;
    brake = false   -- flag indicating if brakes are applied&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Engine constants (using realistic Integra Type R values)&lt;br /&gt;
local MAX_TORQUE = 200   -- in Nm&lt;br /&gt;
&lt;br /&gt;
-- Brake force constant (for active braking)&lt;br /&gt;
local BRAKE_FORCE = 4000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Define a torque curve function:&lt;br /&gt;
local function torqueCurve(rpm)&lt;br /&gt;
    local peakRPM = 6000&lt;br /&gt;
    if rpm &amp;lt; peakRPM then&lt;br /&gt;
        return 0.8 + 0.2 * ((rpm - Engine.idleRPM) / (peakRPM - Engine.idleRPM))&lt;br /&gt;
    else&lt;br /&gt;
        local factor = 1 - 0.5 * ((rpm - peakRPM) / (Engine.maxRPM - peakRPM))&lt;br /&gt;
        return math.max(0.5, factor)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Engine braking force when throttle is zero in gear.&lt;br /&gt;
local ENGINE_BRAKE_FORCE = 2000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Friction and drag constants&lt;br /&gt;
local G = 9.81            -- gravity (m/s^2)&lt;br /&gt;
local C_rr_inGear = 0.015 -- rolling resistance coefficient in gear&lt;br /&gt;
local C_rr_neutral = 0.05 -- rolling resistance coefficient in neutral&lt;br /&gt;
&lt;br /&gt;
local airDensity = 1.225      -- kg/m^3&lt;br /&gt;
local dragCoefficient = 0.3   -- aerodynamic drag coefficient&lt;br /&gt;
local frontalArea = 2.2       -- in m^2&lt;br /&gt;
&lt;br /&gt;
local function getAerodynamicDrag(speed)&lt;br /&gt;
    return 0.5 * airDensity * dragCoefficient * frontalArea * speed * speed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Compute engine RPM from vehicle speed when in gear.&lt;br /&gt;
local function computeEngineRPMFromSpeed()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return Engine.rpm  -- In neutral, RPM is managed independently.&lt;br /&gt;
    else&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local rpm = math.abs(Vehicle.speed) * conversionFactor&lt;br /&gt;
        if rpm &amp;lt; Engine.idleRPM then rpm = Engine.idleRPM end&lt;br /&gt;
        return rpm&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- For neutral, update RPM toward a target based on throttle.&lt;br /&gt;
local function updateEngineRPMNeutral()&lt;br /&gt;
    local targetRPM = Engine.idleRPM + (Engine.maxRPM - Engine.idleRPM) * Engine.throttle&lt;br /&gt;
    local changeRate = 200  -- RPM per tick&lt;br /&gt;
    if Engine.rpm &amp;lt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.min(Engine.rpm + changeRate, targetRPM)&lt;br /&gt;
    elseif Engine.rpm &amp;gt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.max(Engine.rpm - changeRate, targetRPM)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Calculate the engine force delivered to the wheels.&lt;br /&gt;
local function getEngineForce()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return 0  -- In neutral, no drive force is transmitted.&lt;br /&gt;
    else&lt;br /&gt;
        if Engine.throttle == 0 then&lt;br /&gt;
            -- Apply engine braking when throttle is released.&lt;br /&gt;
            return -ENGINE_BRAKE_FORCE&lt;br /&gt;
        else&lt;br /&gt;
            local torque = Engine.throttle * MAX_TORQUE * torqueCurve(Engine.rpm)&lt;br /&gt;
            local force = (torque * Engine.gearRatios[Engine.gear] * FINAL_DRIVE) / WHEEL_RADIUS&lt;br /&gt;
            return force&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Starts the engine by setting it to idle RPM.&lt;br /&gt;
local function startEngine()&lt;br /&gt;
    Engine.rpm = Engine.idleRPM&lt;br /&gt;
    ll.Say(0, &amp;quot;Engine started. RPM: &amp;quot; .. Engine.rpm)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Shifts the gear if valid.&lt;br /&gt;
local function shiftGear(newGear)&lt;br /&gt;
    if Engine.gearRatios[newGear] then&lt;br /&gt;
        Engine.gear = newGear&lt;br /&gt;
        ll.Say(0, &amp;quot;Shifted to gear &amp;quot; .. newGear)&lt;br /&gt;
    else&lt;br /&gt;
        ll.Say(0, &amp;quot;Invalid gear: &amp;quot; .. newGear)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function findWheels()&lt;br /&gt;
    local total = ll.GetNumberOfPrims()&lt;br /&gt;
    for i = 1, total do&lt;br /&gt;
        local name = ll.GetLinkName(i)&lt;br /&gt;
        if name == &amp;quot;WHEEL_LF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_LEFT = i&lt;br /&gt;
        elseif name == &amp;quot;WHEEL_RF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_RIGHT = i&lt;br /&gt;
        end&lt;br /&gt;
    end &lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local castRayCounter = 0&lt;br /&gt;
local lastGrounded = false&lt;br /&gt;
&lt;br /&gt;
local function isVehicleOnGround()&lt;br /&gt;
    castRayCounter = castRayCounter + 1&lt;br /&gt;
    if castRayCounter &amp;gt;= 5 then&lt;br /&gt;
        castRayCounter = 0&lt;br /&gt;
        local pos = ll.GetPos()&lt;br /&gt;
        local rayDistance = 4.5  -- threshold distance (in meters) to consider as &amp;quot;grounded&amp;quot;&lt;br /&gt;
        local rayStart = pos&lt;br /&gt;
        local rayEnd = pos + vector(0, 0, -rayDistance)&lt;br /&gt;
        local hitResult = ll.CastRay(rayStart, rayEnd, {})&lt;br /&gt;
        if table.getn(hitResult) == 3 then&lt;br /&gt;
            lastGrounded = true&lt;br /&gt;
        else&lt;br /&gt;
            lastGrounded = false&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return lastGrounded&lt;br /&gt;
end &lt;br /&gt;
&lt;br /&gt;
-- Timer event: updates engine, vehicle physics, steering, and rotates the wheels based on steering.&lt;br /&gt;
function timer()&lt;br /&gt;
    local dt = 0.1  -- time step in seconds&lt;br /&gt;
&lt;br /&gt;
    -- For neutral, update RPM based on throttle.&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        updateEngineRPMNeutral()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Calculate forces.&lt;br /&gt;
    local C_rr = (Engine.gear == 0) and C_rr_neutral or C_rr_inGear&lt;br /&gt;
    local F_roll = Vehicle.mass * G * C_rr&lt;br /&gt;
    local engineForce = getEngineForce()&lt;br /&gt;
    local dragForce = getAerodynamicDrag(math.abs(Vehicle.speed))&lt;br /&gt;
    local netForce = engineForce - (F_roll + dragForce)&lt;br /&gt;
&lt;br /&gt;
    -- Apply braking force if brakes are engaged.&lt;br /&gt;
    if Vehicle.brake then&lt;br /&gt;
        netForce = netForce - BRAKE_FORCE&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local acceleration = netForce / Vehicle.mass&lt;br /&gt;
    local vel = ll.GetVel()&lt;br /&gt;
    local horizontal_vec = vector(vel.x, vel.y, 0) &lt;br /&gt;
    Vehicle.speed = (ll.VecMag(horizontal_vec) + acceleration * dt)&lt;br /&gt;
&lt;br /&gt;
    -- Limit vehicle speed based on gear.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        if Vehicle.speed &amp;lt; 0 then &lt;br /&gt;
            Vehicle.speed = 0 &lt;br /&gt;
        end&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local maxSpeed = Engine.maxRPM / conversionFactor&lt;br /&gt;
        if Vehicle.speed &amp;gt; maxSpeed then&lt;br /&gt;
            Vehicle.speed = maxSpeed&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- When in gear, update RPM based on the new speed.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        Engine.rpm = computeEngineRPMFromSpeed()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Steering update using angular velocity and quaternions:&lt;br /&gt;
    local STEERING_RATE = math.rad(6)  -- desired steering rate per tick (radians)&lt;br /&gt;
    local yawDelta = Vehicle.steering * STEERING_RATE  -- small rotation for this tick&lt;br /&gt;
&lt;br /&gt;
    if yawDelta ~= 0 then&lt;br /&gt;
        local deltaQuat = ll.Euler2Rot(vector(0, 0, yawDelta))&lt;br /&gt;
        local angularSpeed = yawDelta / dt&lt;br /&gt;
        local angularVelocity = vector(0, 0, angularSpeed)&lt;br /&gt;
        ll.SetAngularVelocity(angularVelocity, 1)&lt;br /&gt;
        Vehicle.angle = Vehicle.angle + yawDelta&lt;br /&gt;
    else&lt;br /&gt;
        ll.SetAngularVelocity(vector(0, 0, 0), 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Display engine RPM and speed (converted to mph).&lt;br /&gt;
    local speed_mph = math.abs(Vehicle.speed) * 2.23694&lt;br /&gt;
    ll.SetText(&amp;quot;RPM: &amp;quot; .. math.floor(Engine.rpm) ..&lt;br /&gt;
               &amp;quot; | Speed: &amp;quot; .. string.format(&amp;quot;%.1f&amp;quot;, speed_mph) ..&lt;br /&gt;
               &amp;quot; mph&amp;quot;, vector(0,0,0), 1)&lt;br /&gt;
&lt;br /&gt;
    -- Only update velocity if the vehicle is near the ground, and we&#039;re in gear.&lt;br /&gt;
    if isVehicleOnGround() and Engine.gear ~= 0 then&lt;br /&gt;
        local velocityVector&lt;br /&gt;
        if Engine.gear &amp;gt; 0 then&lt;br /&gt;
            velocityVector = vector(Vehicle.speed, 0, 0)&lt;br /&gt;
        else&lt;br /&gt;
            -- Reverse gear&lt;br /&gt;
            velocityVector = vector(-Vehicle.speed, 0, 0)&lt;br /&gt;
        end&lt;br /&gt;
        ll.SetVelocity(velocityVector, 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Update the wheel rotations to visually match the steering.&lt;br /&gt;
    local maxWheelTurn = math.rad(30)  -- maximum wheel turn angle in radians&lt;br /&gt;
    local wheelAngle = -Vehicle.steering * maxWheelTurn&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_LEFT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_LEFT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(-4.71239, -wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_RIGHT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_RIGHT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(4.71239, wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched: starts engine and simulation.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    ll.Say(0, &amp;quot;Touch detected, requesting controls and starting engine.&amp;quot;)&lt;br /&gt;
    ll.RequestPermissions(ll.GetOwner(), PERMISSION_TAKE_CONTROLS)&lt;br /&gt;
    startEngine()&lt;br /&gt;
    findWheels()&lt;br /&gt;
    ll.SetTimerEvent(0.1)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when runtime permissions are granted.&lt;br /&gt;
function run_time_permissions(perm)&lt;br /&gt;
    if bit32.band(perm, PERMISSION_TAKE_CONTROLS) then&lt;br /&gt;
        ll.Say(0, &amp;quot;Permissions granted.&amp;quot;)&lt;br /&gt;
        local controlMask = bit32.bor(CONTROL_FWD, CONTROL_BACK, CONTROL_UP, CONTROL_DOWN, CONTROL_LEFT, CONTROL_RIGHT)&lt;br /&gt;
        ll.TakeControls(controlMask, 1, 1)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Handle control events:&lt;br /&gt;
-- • CONTROL_FWD applies throttle.&lt;br /&gt;
-- • CONTROL_BACK applies the brakes.&lt;br /&gt;
-- • CONTROL_UP and CONTROL_DOWN shift gears.&lt;br /&gt;
-- • CONTROL_LEFT and CONTROL_RIGHT steer the vehicle.&lt;br /&gt;
function control(avatar_id, level, edge)&lt;br /&gt;
    local start = bit32.band(level, edge)            &lt;br /&gt;
    local finish = bit32.band(bit32.bnot(level), edge) &lt;br /&gt;
    local held = bit32.band(level, bit32.bnot(edge))   &lt;br /&gt;
    local untouched = bit32.bnot(bit32.bor(level, edge)) &lt;br /&gt;
&lt;br /&gt;
    if bit32.band(held, CONTROL_BACK) ~= 0 then&lt;br /&gt;
        Vehicle.brake = true&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    elseif bit32.band(held, CONTROL_FWD) ~= 0 then&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 1.0&lt;br /&gt;
        if Engine.gear == 0 then&lt;br /&gt;
            updateEngineRPMNeutral()&lt;br /&gt;
        end&lt;br /&gt;
    else&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_UP) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear + 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_DOWN) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear - 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local steeringInput = 0&lt;br /&gt;
    if bit32.band(held, CONTROL_LEFT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput + 1&lt;br /&gt;
    end&lt;br /&gt;
    if bit32.band(held, CONTROL_RIGHT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput - 1&lt;br /&gt;
    end&lt;br /&gt;
    Vehicle.steering = steeringInput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== weather_box.lua ==&lt;br /&gt;
This script demonstrates coroutines, as well as working with a JSON-based web API:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local TEXTBOX_CHANNEL = 323242&lt;br /&gt;
-- kludge because we don&#039;t have LSL constants in here yet&lt;br /&gt;
local HTTP_BODY_MAXLENGTH = integer(2)&lt;br /&gt;
local PRIM_TEXT = integer(26)&lt;br /&gt;
local PRIM_SIZE = integer(7)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
gCityName = &amp;quot;&amp;quot;&lt;br /&gt;
gLatLong = nil  -- A table of 2 elems when it&#039;s populated&lt;br /&gt;
gCityRequestTask = nil&lt;br /&gt;
gWeatherRequestTask = nil&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- JSON stuff&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- From https://github.com/rxi/json.lua/blob/master/json.lua&lt;br /&gt;
&lt;br /&gt;
local json = {}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map = {&lt;br /&gt;
  [ &amp;quot;\\&amp;quot; ] = &amp;quot;\\&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\&amp;quot;&amp;quot; ] = &amp;quot;\&amp;quot;&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\b&amp;quot; ] = &amp;quot;b&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\f&amp;quot; ] = &amp;quot;f&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\n&amp;quot; ] = &amp;quot;n&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\r&amp;quot; ] = &amp;quot;r&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\t&amp;quot; ] = &amp;quot;t&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map_inv = { [ &amp;quot;/&amp;quot; ] = &amp;quot;/&amp;quot; }&lt;br /&gt;
for k, v in pairs(escape_char_map) do&lt;br /&gt;
  escape_char_map_inv[v] = k&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local parse&lt;br /&gt;
&lt;br /&gt;
local function create_set(...)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  for i = 1, select(&amp;quot;#&amp;quot;, ...) do&lt;br /&gt;
    res[ select(i, ...) ] = true&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local space_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;)&lt;br /&gt;
local delim_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, &amp;quot;]&amp;quot;, &amp;quot;}&amp;quot;, &amp;quot;,&amp;quot;)&lt;br /&gt;
local escape_chars  = create_set(&amp;quot;\\&amp;quot;, &amp;quot;/&amp;quot;, &#039;&amp;quot;&#039;, &amp;quot;b&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;r&amp;quot;, &amp;quot;t&amp;quot;, &amp;quot;u&amp;quot;)&lt;br /&gt;
local literals      = create_set(&amp;quot;true&amp;quot;, &amp;quot;false&amp;quot;, &amp;quot;null&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local literal_map = {&lt;br /&gt;
  [ &amp;quot;true&amp;quot;  ] = true,&lt;br /&gt;
  [ &amp;quot;false&amp;quot; ] = false,&lt;br /&gt;
  [ &amp;quot;null&amp;quot;  ] = nil,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function next_char(str, idx, set, negate)&lt;br /&gt;
  for i = idx, #str do&lt;br /&gt;
    if set[str:sub(i, i)] ~= negate then&lt;br /&gt;
      return i&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  return #str + 1&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function decode_error(str, idx, msg)&lt;br /&gt;
  local line_count = 1&lt;br /&gt;
  local col_count = 1&lt;br /&gt;
  for i = 1, idx - 1 do&lt;br /&gt;
    col_count = col_count + 1&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;\n&amp;quot; then&lt;br /&gt;
      line_count = line_count + 1&lt;br /&gt;
      col_count = 1&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;%s at line %d col %d&amp;quot;, msg, line_count, col_count) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function codepoint_to_utf8(n)&lt;br /&gt;
  -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&amp;amp;id=iws-appendixa&lt;br /&gt;
  local f = math.floor&lt;br /&gt;
  if n &amp;lt;= 0x7f then&lt;br /&gt;
    return string.char(n)&lt;br /&gt;
  elseif n &amp;lt;= 0x7ff then&lt;br /&gt;
    return string.char(f(n / 64) + 192, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0xffff then&lt;br /&gt;
    return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0x10ffff then&lt;br /&gt;
    return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,&lt;br /&gt;
                       f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;invalid unicode codepoint &#039;%x&#039;&amp;quot;, n) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_unicode_escape(s)&lt;br /&gt;
  local n1 = tonumber( s:sub(1, 4),  16 )&lt;br /&gt;
  local n2 = tonumber( s:sub(7, 10), 16 )&lt;br /&gt;
   -- Surrogate pair?&lt;br /&gt;
  if n2 then&lt;br /&gt;
    return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)&lt;br /&gt;
  else&lt;br /&gt;
    return codepoint_to_utf8(n1)&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_string(str, i)&lt;br /&gt;
  local res = &amp;quot;&amp;quot;&lt;br /&gt;
  local j = i + 1&lt;br /&gt;
  local k = j&lt;br /&gt;
&lt;br /&gt;
  while j &amp;lt;= #str do&lt;br /&gt;
    local x = str:byte(j)&lt;br /&gt;
&lt;br /&gt;
    if x &amp;lt; 32 then&lt;br /&gt;
      decode_error(str, j, &amp;quot;control character in string&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    elseif x == 92 then -- `\`: Escape&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      j = j + 1&lt;br /&gt;
      local c = str:sub(j, j)&lt;br /&gt;
      if c == &amp;quot;u&amp;quot; then&lt;br /&gt;
        local hex = str:match(&amp;quot;^[dD][89aAbB]%x%x\\u%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or str:match(&amp;quot;^%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or decode_error(str, j - 1, &amp;quot;invalid unicode escape in string&amp;quot;)&lt;br /&gt;
        res = res .. parse_unicode_escape(hex)&lt;br /&gt;
        j = j + #hex&lt;br /&gt;
      else&lt;br /&gt;
        if not escape_chars[c] then&lt;br /&gt;
          decode_error(str, j - 1, &amp;quot;invalid escape char &#039;&amp;quot; .. c .. &amp;quot;&#039; in string&amp;quot;)&lt;br /&gt;
        end&lt;br /&gt;
        res = res .. escape_char_map_inv[c]&lt;br /&gt;
      end&lt;br /&gt;
      k = j + 1&lt;br /&gt;
&lt;br /&gt;
    elseif x == 34 then -- `&amp;quot;`: End of string&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      return res, j + 1&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    j = j + 1&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  decode_error(str, i, &amp;quot;expected closing quote for string&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_number(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local s = str:sub(i, x - 1)&lt;br /&gt;
  local n = tonumber(s)&lt;br /&gt;
  if not n then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid number &#039;&amp;quot; .. s .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return n, x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_literal(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local word = str:sub(i, x - 1)&lt;br /&gt;
  if not literals[word] then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid literal &#039;&amp;quot; .. word .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return literal_map[word], x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_array(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  local n = 1&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local x&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of array?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;]&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read token&lt;br /&gt;
    x, i = parse(str, i)&lt;br /&gt;
    res[n] = x&lt;br /&gt;
    n = n + 1&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;]&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;]&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_object(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local key, val&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of object?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;}&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read key&lt;br /&gt;
    if str:sub(i, i) ~= &#039;&amp;quot;&#039; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected string for key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    key, i = parse(str, i)&lt;br /&gt;
    -- Read &#039;:&#039; delimiter&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    if str:sub(i, i) ~= &amp;quot;:&amp;quot; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected &#039;:&#039; after key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    i = next_char(str, i + 1, space_chars, true)&lt;br /&gt;
    -- Read value&lt;br /&gt;
    val, i = parse(str, i)&lt;br /&gt;
    -- Set&lt;br /&gt;
    res[key] = val&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;}&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;}&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local char_func_map = {&lt;br /&gt;
  [ &#039;&amp;quot;&#039; ] = parse_string,&lt;br /&gt;
  [ &amp;quot;0&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;1&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;2&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;3&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;4&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;5&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;6&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;7&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;8&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;9&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;-&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;t&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;f&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;n&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;[&amp;quot; ] = parse_array,&lt;br /&gt;
  [ &amp;quot;{&amp;quot; ] = parse_object,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
parse = function(str, idx)&lt;br /&gt;
  local chr = str:sub(idx, idx)&lt;br /&gt;
  local f = char_func_map[chr]&lt;br /&gt;
  if f then&lt;br /&gt;
    return f(str, idx)&lt;br /&gt;
  end&lt;br /&gt;
  decode_error(str, idx, &amp;quot;unexpected character &#039;&amp;quot; .. chr .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function json.decode(str)&lt;br /&gt;
  if type(str) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
    error(&amp;quot;expected argument of type string, got &amp;quot; .. type(str))&lt;br /&gt;
  end&lt;br /&gt;
  local res, idx = parse(str, next_char(str, 1, space_chars, true))&lt;br /&gt;
  idx = next_char(str, idx, space_chars, true)&lt;br /&gt;
  if idx &amp;lt;= #str then&lt;br /&gt;
    decode_error(str, idx, &amp;quot;trailing garbage&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Loop&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- A naive little event loop that lets you track running coroutines and dispatches&lt;br /&gt;
-- the events they&#039;re waiting for to them.&lt;br /&gt;
&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; awaited event&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    -- false has no semantic meaning here, we just want to reserve the space for&lt;br /&gt;
    -- the coroutine in the table without saying we&#039;re awaiting anything yet.&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- (internal) run the event&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    assert(coroutine.status(coro) ~= &amp;quot;dead&amp;quot;)&lt;br /&gt;
    -- assert(not self.running)&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    -- Since we&#039;re running, this isn&#039;t currently awaiting anything&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local success, ret = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    -- If we&#039;re not done then ret should be the kind of event we&#039;re awaiting.&lt;br /&gt;
    -- Otherwise it&#039;s an error message or something. Who cares.&lt;br /&gt;
    if not success then&lt;br /&gt;
        -- Get rid of the coro&lt;br /&gt;
        if ret then&lt;br /&gt;
            ll.OwnerSay(`Might have an error: {ret}`)&lt;br /&gt;
        end&lt;br /&gt;
        ret = nil&lt;br /&gt;
    end&lt;br /&gt;
    self._coros[coro] = ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Wake up any tasks that are waiting for this kind of event&lt;br /&gt;
function EventLoop:handle_event(name, ...)&lt;br /&gt;
    -- We may mutate self._coros, so do a clone&lt;br /&gt;
    local coros = table.clone(self._coros)&lt;br /&gt;
    ll.OwnerSay(`Handling {name}`)&lt;br /&gt;
&lt;br /&gt;
    for coro, awaited_event in coros do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            -- Hmm, this coroutine is dead, prune it.&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if awaited_event == name then&lt;br /&gt;
            ll.OwnerSay(`Dispatching {name} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_event_catcher(name)&lt;br /&gt;
    local function catcher(...)&lt;br /&gt;
        ll.OwnerSay(&amp;quot;yay&amp;quot;)&lt;br /&gt;
        EventLoop:handle_event(name, ...)&lt;br /&gt;
    end&lt;br /&gt;
    return catcher&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Helper function for waiting and telling the EventLoop what event you want&lt;br /&gt;
local function await_event(kind)&lt;br /&gt;
    -- You had better do this inside a running event loop&lt;br /&gt;
    assert(EventLoop.running)&lt;br /&gt;
    -- This will yield, the EventLoop will resume us once it has&lt;br /&gt;
    -- an event we might be interested in.&lt;br /&gt;
    return coroutine.yield(kind)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Script-specific functions&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
local function hide_children()&lt;br /&gt;
    for i=2,ll.GetNumberOfPrims() do&lt;br /&gt;
        -- Hide all the child prims&lt;br /&gt;
        ll.SetLinkAlpha(i, 0, -1)&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(i, {&lt;br /&gt;
            PRIM_SIZE, vector(0.5, 0.5, 0.5),&lt;br /&gt;
            PRIM_TEXT, &amp;quot;&amp;quot;, vector(1,1,1), 1&lt;br /&gt;
        })&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_textbox(avatar_id, message, channel)&lt;br /&gt;
    ll.TextBox(avatar_id, message, channel)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        local resp_channel, name, id, resp = await_event(&amp;quot;listen&amp;quot;)&lt;br /&gt;
        if id ~= avatar_id or channel ~= resp_channel then&lt;br /&gt;
            -- If this isn&#039;t a message we&#039;re interested in then wait for the next event&lt;br /&gt;
            ll.OwnerSay(`{id}, {avatar_id}, {resp_channel}, {channel}`)&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return resp&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function make_json_request(url)&lt;br /&gt;
    local req_id = ll.HTTPRequest(url, {HTTP_BODY_MAXLENGTH, integer(16000)}, &amp;quot;&amp;quot;)&lt;br /&gt;
    while true do&lt;br /&gt;
        local ev_id, status, metadata, body = await_event(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
        if ev_id ~= req_id then&lt;br /&gt;
            -- Not a response for the HTTP request we were waiting on, wait for the next event.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return status, metadata, body&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function request_weather()&lt;br /&gt;
    -- And you can use a wrapper like this to make an async function callable within both normal and coroutine contexts.&lt;br /&gt;
    -- Normally this sort of thing would not be necessary, but here it is since the EventLoop lives in our own code,&lt;br /&gt;
    -- and isn&#039;t a property of the actual script engine.&lt;br /&gt;
    if gWeatherRequestTask then&lt;br /&gt;
        EventLoop:kill_task(gWeatherRequestTask)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    gWeatherRequestTask = EventLoop:create_task(function()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Requesting weather&amp;quot;)&lt;br /&gt;
        local status, metadata, body = make_json_request(`https://api.open-meteo.com/v1/forecast?latitude={gLatLong[1]}&amp;amp;longitude={gLatLong[2]}&amp;amp;current=temperature_2m&amp;amp;daily=temperature_2m_max`)&lt;br /&gt;
        if status ~= 200 then&lt;br /&gt;
            ll.OwnerSay(&amp;quot;Error requesting weather&amp;quot;)&lt;br /&gt;
            return&lt;br /&gt;
        end&lt;br /&gt;
        local parsed = json.decode(body)&lt;br /&gt;
&lt;br /&gt;
        local current = parsed[&amp;quot;current&amp;quot;]&lt;br /&gt;
        ll.OwnerSay(`Temperature is {current[&amp;quot;temperature_2m&amp;quot;]}C`)&lt;br /&gt;
        ll.SetText(`Temperature in {gCityName} is currently {current[&amp;quot;temperature_2m&amp;quot;]}C`, vector(1,1,1), 1)&lt;br /&gt;
&lt;br /&gt;
        -- Show the highs for each day&lt;br /&gt;
        local days = parsed[&amp;quot;daily&amp;quot;][&amp;quot;temperature_2m_max&amp;quot;]&lt;br /&gt;
        for i=1,#days do&lt;br /&gt;
            local link_num = i + 1&lt;br /&gt;
            -- Higher temps = bigger on Z, negatives are unrepresentable :)&lt;br /&gt;
            local z_size = (days[i] / 30) * 2&lt;br /&gt;
            ll.SetLinkAlpha(link_num, 1, -1)&lt;br /&gt;
            ll.SetLinkPrimitiveParamsFast(link_num, {&lt;br /&gt;
                PRIM_SIZE, vector(0.5, 0.5, z_size),&lt;br /&gt;
                PRIM_TEXT, `{days[i]}C`, vector(1,1,1), 1&lt;br /&gt;
            })&lt;br /&gt;
        end&lt;br /&gt;
    end)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ask_for_city()&lt;br /&gt;
    local resp = send_textbox(ll.GetOwner(), &amp;quot;What city do you want the weather for?&amp;quot;, TEXTBOX_CHANNEL)&lt;br /&gt;
    if not resp then&lt;br /&gt;
        ll.OwnerSay(&amp;quot;Got no response for city request.&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    ll.OwnerSay(`You want the weather for {resp}.`)&lt;br /&gt;
    gLatLong = nil&lt;br /&gt;
    gCityName = resp&lt;br /&gt;
&lt;br /&gt;
    hide_children()&lt;br /&gt;
&lt;br /&gt;
    -- request the lat and long for this city&lt;br /&gt;
    local status, metadata, body = make_json_request(`https://nominatim.openstreetmap.org/search.php?city={ll.EscapeURL(resp)}&amp;amp;format=jsonv2`)&lt;br /&gt;
    local parsed = json.decode(body)&lt;br /&gt;
    if not #parsed then&lt;br /&gt;
        ll.OwnerSay(`Got no results for {resp}!`)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local first_result = parsed[1]&lt;br /&gt;
    gLatLong = {first_result[&amp;quot;lat&amp;quot;], first_result[&amp;quot;lon&amp;quot;]}&lt;br /&gt;
    ll.OwnerSay(`City Lat: {gLatLong[1]}, Long: {gLatLong[2]}`)&lt;br /&gt;
&lt;br /&gt;
    request_weather()&lt;br /&gt;
&lt;br /&gt;
    -- request weather again in 3 mins&lt;br /&gt;
    ll.SetTimerEvent(180)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Handlers&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- If we don&#039;t care about conventionally dispatched events, we can just set the event handler to directly send&lt;br /&gt;
-- event data to the event loop.&lt;br /&gt;
http_response = EventLoop:create_event_catcher(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
listen = EventLoop:create_event_catcher(&amp;quot;listen&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- More conventional event handlers work too.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    for i=0,num-1 do&lt;br /&gt;
        if ll.GetOwner() ~= ll.DetectedKey(i) then&lt;br /&gt;
            -- Go to next loop if it wasn&#039;t the owner touching us.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if gCityRequestTask then&lt;br /&gt;
            EventLoop:kill_task(gCityRequestTask)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Spin up a task to ask the user for their city&lt;br /&gt;
        gCityRequestTask = EventLoop:create_task(ask_for_city)&lt;br /&gt;
        break&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function timer()&lt;br /&gt;
    request_weather()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Not strictly necessary, but I like having the main function have its own scope.&lt;br /&gt;
-- Rather than just have top-level logic.&lt;br /&gt;
local function main()&lt;br /&gt;
    ll.Listen(TEXTBOX_CHANNEL, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.SetText(&amp;quot;&amp;quot;, vector(1,1,1), 1)&lt;br /&gt;
    hide_children()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Weatherbox operational!&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
main()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Luau&amp;diff=1218012</id>
		<title>Luau</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Luau&amp;diff=1218012"/>
		<updated>2025-03-04T22:47:24Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Signal Linden moved page Luau to Luau Alpha over redirect&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Luau Alpha]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218011</id>
		<title>Lua Alpha</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218011"/>
		<updated>2025-03-04T22:47:17Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Signal Linden moved page Luau to Luau Alpha over redirect&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{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.}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Second Life Luau Alpha =&lt;br /&gt;
&lt;br /&gt;
[[File:Luau.png|720px|thumb|right|Luau logo]]&lt;br /&gt;
&lt;br /&gt;
We&#039;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.&lt;br /&gt;
&lt;br /&gt;
== What is Luau? ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Why Luau? ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== How to Get Started with Luau in Second Life ==&lt;br /&gt;
&lt;br /&gt;
In order to play with Luau, you&#039;ll need to download our Lua project viewer, and log onto our Aditi beta grid.&lt;br /&gt;
&lt;br /&gt;
* Access the latest build of the Luau-enabled Second Life Viewer from [https://releasenotes.secondlife.com/viewer/7.1.12.13526902562.html here].&lt;br /&gt;
&lt;br /&gt;
Once you&#039;ve got the new viewer and have logged onto the beta grid, head over to these Luau-enabled regions:&lt;br /&gt;
* Luau Yardang&lt;br /&gt;
* Luau Tombolo&lt;br /&gt;
* Luau Mesa&lt;br /&gt;
* Luau Tideland&lt;br /&gt;
&lt;br /&gt;
When editing a script in the new Lua project viewer, you&#039;ll notice a new &#039;&#039;&#039;Compiler&#039;&#039;&#039; 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).&lt;br /&gt;
&lt;br /&gt;
[[File:Compiler_dropdown.png|Compiler selection dropdown]]&lt;br /&gt;
&lt;br /&gt;
Compiler drop-down options:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;LSL: Legacy (LSO2)&#039;&#039;&#039; - Scripts written in LSL, to be run on the old LSO2 VM&lt;br /&gt;
* &#039;&#039;&#039;LSL: Mono&#039;&#039;&#039;- Scripts written in LSL, to be run on the Mono VM&lt;br /&gt;
* &#039;&#039;&#039;Lua&#039;&#039;&#039; - Scripts written in Lua, to be run on the Luau VM&lt;br /&gt;
* &#039;&#039;&#039;LSL/Luau&#039;&#039;&#039;- Scripts written in LSL, to be run on the Luau VM&lt;br /&gt;
&lt;br /&gt;
=== Transitioning from LSL to Luau ===&lt;br /&gt;
* &#039;&#039;&#039;Function Namespacing:&#039;&#039;&#039;&lt;br /&gt;
** In Luau, Linden Lab functions have been moved under the &#039;&#039;&#039;ll&#039;&#039;&#039; namespace.&lt;br /&gt;
** For example:&lt;br /&gt;
*** &#039;&#039;llSay&#039;&#039; becomes &#039;&#039;ll.Say&#039;&#039;&lt;br /&gt;
*** &#039;&#039;llGetPos&#039;&#039; becomes &#039;&#039;ll.GetPos&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Constants:&#039;&#039;&#039;&lt;br /&gt;
** Constants like &#039;&#039;PERMISSION_TAKE_CONTROLS&#039;&#039; and &#039;&#039;PRIM_ROT_LOCAL&#039;&#039; are not predefined in Luau.&lt;br /&gt;
** You must specify these constants if you wish to reference them in your scripts.&lt;br /&gt;
&lt;br /&gt;
=== Luau Libraries ===&lt;br /&gt;
* &#039;&#039;&#039;Coroutines:&#039;&#039;&#039;&lt;br /&gt;
** Luau supports coroutines, allowing for cooperative multitasking within scripts.&lt;br /&gt;
** Key functions include:&lt;br /&gt;
*** &#039;&#039;coroutine.create&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.status&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.resume&#039;&#039;&lt;br /&gt;
** Refer to the [https://luau.org/library#coroutine-library coroutine library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Bitwise Operations:&#039;&#039;&#039;&lt;br /&gt;
** Luau includes a &#039;&#039;bit32&#039;&#039; library for bitwise operations, enabling more efficient data manipulation.&lt;br /&gt;
** Refer to the [https://luau.org/library#bit32-library bit32 library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Standard Library:&#039;&#039;&#039;&lt;br /&gt;
** Luau comes equipped with a standard library of functions designed to manipulate built-in data types.&lt;br /&gt;
** Explore the [https://luau.org/library Luau Standard Library] for a comprehensive list of available functions.&lt;br /&gt;
&lt;br /&gt;
== Feedback and Support ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== Example Scripts ==&lt;br /&gt;
&lt;br /&gt;
To help you get started, we&#039;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&#039;s desire!&lt;br /&gt;
&lt;br /&gt;
=== dialog.lua ===&lt;br /&gt;
This script demonstrates how one can interact with dialog menus.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Define the menu buttons and dialog message.&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local ToucherID = nil&lt;br /&gt;
local dialogChannel = nil&lt;br /&gt;
local listenHandle = nil&lt;br /&gt;
&lt;br /&gt;
-- This function is called when the script first starts.&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Get the object&#039;s key and compute a dialog channel number.&lt;br /&gt;
    local key = ll.GetKey()&lt;br /&gt;
    -- Extract the last 7 characters of the key and convert it from hex.&lt;br /&gt;
    dialogChannel = -1 - tonumber(string.sub(tostring(key), -7, -1), 16)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    ToucherID = ll.DetectedKey(0)&lt;br /&gt;
    -- If there is already a listen handle, then remove it&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
    end&lt;br /&gt;
    listenHandle = ll.Listen(dialogChannel, &amp;quot;&amp;quot;, ToucherID, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
    -- Set a 60-second timer for response.&lt;br /&gt;
    ll.SetTimerEvent(60.0)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when a dialog response is received.&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    if message == &amp;quot;-&amp;quot; then&lt;br /&gt;
        -- Redisplay the dialog if the &amp;quot;-&amp;quot; option is selected.&lt;br /&gt;
        ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Stop the timer, and stop the listening handler.&lt;br /&gt;
    ll.ListenRemove(listenHandle)&lt;br /&gt;
    ll.SetTimerEvent(0)&lt;br /&gt;
    -- Let the user know what they selected&lt;br /&gt;
    ll.Say(0, `You selected {message}`)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the timer expires.&lt;br /&gt;
function timer()&lt;br /&gt;
    -- Stop the timer and clean up the listener.&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.SetTimerEvent(0)&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
        ll.Whisper(0, &amp;quot;Sorry. You snooze; you lose.&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dialog_coroutine.lua ===&lt;br /&gt;
This script demonstrates how one could use coroutines to handle dialog responses, with multi-user support.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Minimal EventLoop&lt;br /&gt;
-------------------------&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; eventName it’s waiting for&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status(coro) ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Internal helper: resumes a coroutine&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local ok, eventAwaited = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    if not ok then&lt;br /&gt;
        ll.OwnerSay(`Coroutine error: {eventAwaited}`)&lt;br /&gt;
        self._coros[coro] = nil&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- If still alive, &#039;eventAwaited&#039; is the next event it wants&lt;br /&gt;
    self._coros[coro] = eventAwaited&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:handle_event(eventName, ...)&lt;br /&gt;
    ll.OwnerSay(`Handling event {eventName}`)&lt;br /&gt;
    local snapshot = table.clone(self._coros)&lt;br /&gt;
    for coro, waitingFor in pairs(snapshot) do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        elseif waitingFor == eventName then&lt;br /&gt;
            ll.OwnerSay(`Dispatching event {eventName} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Coroutines use this to yield until an event&lt;br /&gt;
local function await_event(name)&lt;br /&gt;
    assert(EventLoop.running, &amp;quot;await_event called outside a coroutine!&amp;quot;)&lt;br /&gt;
    return coroutine.yield(name)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Script Logic&lt;br /&gt;
-------------------------&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- Use the chat listener to feed the event-loop&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    -- We handle all &#039;listen&#039; events via the event-loop&lt;br /&gt;
    EventLoop:handle_event(`listen_{channel}`, channel, name, sender_id, message)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the script starts&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Seed math.random so each new script run doesn’t repeat the same channels&lt;br /&gt;
    math.randomseed(ll.GetUnixTime())&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Script started with random channels for each user.&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- A coroutine function for a single user&#039;s dialog flow&lt;br /&gt;
local function handle_dialog_for_user(userId)&lt;br /&gt;
    -- Use a random channel&lt;br /&gt;
    local channel = math.random(0x1, 0xFFFF)&lt;br /&gt;
    -- Create a listener for that channel&lt;br /&gt;
    local listenHandle = ll.Listen(channel, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        -- Show the user a dialog&lt;br /&gt;
        ll.Dialog(userId, dialogInfo, buttons, channel)&lt;br /&gt;
&lt;br /&gt;
        -- Wait for the next &#039;listen&#039; event (channel, name, sender_id, message)&lt;br /&gt;
        local c, n, sid, msg = await_event(`listen_{channel}`)&lt;br /&gt;
&lt;br /&gt;
        -- If this &amp;quot;listen&amp;quot; event isn&#039;t for our channel/user, ignore it&lt;br /&gt;
        if c == channel and sid == userId then&lt;br /&gt;
            -- If user pressed &amp;quot;-&amp;quot;, re-display the menu, else they picked a final color&lt;br /&gt;
            if msg ~= &amp;quot;-&amp;quot; then&lt;br /&gt;
                ll.Say(0, `{n} selected {msg}`)&lt;br /&gt;
                -- Now that they&#039;ve chosen something else, remove the listener and finish&lt;br /&gt;
                ll.ListenRemove(listenHandle)&lt;br /&gt;
                return&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    for i=0, num_detected-1 do&lt;br /&gt;
        local toucherId = ll.DetectedKey(i)&lt;br /&gt;
        -- Create a separate coroutine for each person who touches&lt;br /&gt;
        EventLoop:create_task(function()&lt;br /&gt;
            handle_dialog_for_user(toucherId)&lt;br /&gt;
        end)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Run state_entry on load:&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== vehicle.lua ===&lt;br /&gt;
This script demonstrates how one may create their own vehicle scripts, which don’t rely on the underlying LL vehicle system. &lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Control and permission constants&lt;br /&gt;
local PERMISSION_TAKE_CONTROLS = 0x4&lt;br /&gt;
local CONTROL_FWD       = 0x1&lt;br /&gt;
local CONTROL_BACK      = 0x2&lt;br /&gt;
local CONTROL_UP        = 0x10&lt;br /&gt;
local CONTROL_DOWN      = 0x20&lt;br /&gt;
local CONTROL_LEFT      = 0x4&lt;br /&gt;
local CONTROL_RIGHT     = 0x8&lt;br /&gt;
local PRIM_ROT_LOCAL    = integer(29)&lt;br /&gt;
&lt;br /&gt;
-- These two will hold the link numbers for left and right front wheels&lt;br /&gt;
local LINK_WHEEL_LEFT = 0&lt;br /&gt;
local LINK_WHEEL_RIGHT = 0&lt;br /&gt;
&lt;br /&gt;
-- Integra Type R Engine properties&lt;br /&gt;
local Engine = {&lt;br /&gt;
    rpm = 800,              -- current engine RPM (starts at idle)&lt;br /&gt;
    gear = 0,               -- 0 = neutral; -1 = reverse; 1-5 for forward gears&lt;br /&gt;
    idleRPM = 800,          -- idle RPM&lt;br /&gt;
    maxRPM = 8300,          -- redline&lt;br /&gt;
    throttle = 0.0,         -- throttle value (0.0 to 1.0)&lt;br /&gt;
    gearRatios = {          -- Integra Type R gear ratios&lt;br /&gt;
        [-1] = 3.727,       -- reverse gear ratio&lt;br /&gt;
        [0]  = 0,          -- neutral&lt;br /&gt;
        [1]  = 3.727,&lt;br /&gt;
        [2]  = 2.256,&lt;br /&gt;
        [3]  = 1.729,&lt;br /&gt;
        [4]  = 1.416,&lt;br /&gt;
        [5]  = 1.194&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FINAL_DRIVE = 4.1   -- Final drive ratio&lt;br /&gt;
local WHEEL_RADIUS = 0.3  -- in meters&lt;br /&gt;
&lt;br /&gt;
-- Vehicle properties (using realistic Integra Type R mass)&lt;br /&gt;
local Vehicle = {&lt;br /&gt;
    speed = 0,      -- speed in m/s; negative indicates reverse motion&lt;br /&gt;
    mass = 1200,    -- mass in kg&lt;br /&gt;
    angle = 0,      -- heading angle in radians&lt;br /&gt;
    steering = 0,   -- steering input: -1 (left), 0 (none), 1 (right)&lt;br /&gt;
    brake = false   -- flag indicating if brakes are applied&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Engine constants (using realistic Integra Type R values)&lt;br /&gt;
local MAX_TORQUE = 200   -- in Nm&lt;br /&gt;
&lt;br /&gt;
-- Brake force constant (for active braking)&lt;br /&gt;
local BRAKE_FORCE = 4000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Define a torque curve function:&lt;br /&gt;
local function torqueCurve(rpm)&lt;br /&gt;
    local peakRPM = 6000&lt;br /&gt;
    if rpm &amp;lt; peakRPM then&lt;br /&gt;
        return 0.8 + 0.2 * ((rpm - Engine.idleRPM) / (peakRPM - Engine.idleRPM))&lt;br /&gt;
    else&lt;br /&gt;
        local factor = 1 - 0.5 * ((rpm - peakRPM) / (Engine.maxRPM - peakRPM))&lt;br /&gt;
        return math.max(0.5, factor)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Engine braking force when throttle is zero in gear.&lt;br /&gt;
local ENGINE_BRAKE_FORCE = 2000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Friction and drag constants&lt;br /&gt;
local G = 9.81            -- gravity (m/s^2)&lt;br /&gt;
local C_rr_inGear = 0.015 -- rolling resistance coefficient in gear&lt;br /&gt;
local C_rr_neutral = 0.05 -- rolling resistance coefficient in neutral&lt;br /&gt;
&lt;br /&gt;
local airDensity = 1.225      -- kg/m^3&lt;br /&gt;
local dragCoefficient = 0.3   -- aerodynamic drag coefficient&lt;br /&gt;
local frontalArea = 2.2       -- in m^2&lt;br /&gt;
&lt;br /&gt;
local function getAerodynamicDrag(speed)&lt;br /&gt;
    return 0.5 * airDensity * dragCoefficient * frontalArea * speed * speed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Compute engine RPM from vehicle speed when in gear.&lt;br /&gt;
local function computeEngineRPMFromSpeed()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return Engine.rpm  -- In neutral, RPM is managed independently.&lt;br /&gt;
    else&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local rpm = math.abs(Vehicle.speed) * conversionFactor&lt;br /&gt;
        if rpm &amp;lt; Engine.idleRPM then rpm = Engine.idleRPM end&lt;br /&gt;
        return rpm&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- For neutral, update RPM toward a target based on throttle.&lt;br /&gt;
local function updateEngineRPMNeutral()&lt;br /&gt;
    local targetRPM = Engine.idleRPM + (Engine.maxRPM - Engine.idleRPM) * Engine.throttle&lt;br /&gt;
    local changeRate = 200  -- RPM per tick&lt;br /&gt;
    if Engine.rpm &amp;lt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.min(Engine.rpm + changeRate, targetRPM)&lt;br /&gt;
    elseif Engine.rpm &amp;gt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.max(Engine.rpm - changeRate, targetRPM)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Calculate the engine force delivered to the wheels.&lt;br /&gt;
local function getEngineForce()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return 0  -- In neutral, no drive force is transmitted.&lt;br /&gt;
    else&lt;br /&gt;
        if Engine.throttle == 0 then&lt;br /&gt;
            -- Apply engine braking when throttle is released.&lt;br /&gt;
            return -ENGINE_BRAKE_FORCE&lt;br /&gt;
        else&lt;br /&gt;
            local torque = Engine.throttle * MAX_TORQUE * torqueCurve(Engine.rpm)&lt;br /&gt;
            local force = (torque * Engine.gearRatios[Engine.gear] * FINAL_DRIVE) / WHEEL_RADIUS&lt;br /&gt;
            return force&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Starts the engine by setting it to idle RPM.&lt;br /&gt;
local function startEngine()&lt;br /&gt;
    Engine.rpm = Engine.idleRPM&lt;br /&gt;
    ll.Say(0, &amp;quot;Engine started. RPM: &amp;quot; .. Engine.rpm)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Shifts the gear if valid.&lt;br /&gt;
local function shiftGear(newGear)&lt;br /&gt;
    if Engine.gearRatios[newGear] then&lt;br /&gt;
        Engine.gear = newGear&lt;br /&gt;
        ll.Say(0, &amp;quot;Shifted to gear &amp;quot; .. newGear)&lt;br /&gt;
    else&lt;br /&gt;
        ll.Say(0, &amp;quot;Invalid gear: &amp;quot; .. newGear)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function findWheels()&lt;br /&gt;
    local total = ll.GetNumberOfPrims()&lt;br /&gt;
    for i = 1, total do&lt;br /&gt;
        local name = ll.GetLinkName(i)&lt;br /&gt;
        if name == &amp;quot;WHEEL_LF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_LEFT = i&lt;br /&gt;
        elseif name == &amp;quot;WHEEL_RF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_RIGHT = i&lt;br /&gt;
        end&lt;br /&gt;
    end &lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local castRayCounter = 0&lt;br /&gt;
local lastGrounded = false&lt;br /&gt;
&lt;br /&gt;
local function isVehicleOnGround()&lt;br /&gt;
    castRayCounter = castRayCounter + 1&lt;br /&gt;
    if castRayCounter &amp;gt;= 5 then&lt;br /&gt;
        castRayCounter = 0&lt;br /&gt;
        local pos = ll.GetPos()&lt;br /&gt;
        local rayDistance = 4.5  -- threshold distance (in meters) to consider as &amp;quot;grounded&amp;quot;&lt;br /&gt;
        local rayStart = pos&lt;br /&gt;
        local rayEnd = pos + vector(0, 0, -rayDistance)&lt;br /&gt;
        local hitResult = ll.CastRay(rayStart, rayEnd, {})&lt;br /&gt;
        if table.getn(hitResult) == 3 then&lt;br /&gt;
            lastGrounded = true&lt;br /&gt;
        else&lt;br /&gt;
            lastGrounded = false&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return lastGrounded&lt;br /&gt;
end &lt;br /&gt;
&lt;br /&gt;
-- Timer event: updates engine, vehicle physics, steering, and rotates the wheels based on steering.&lt;br /&gt;
function timer()&lt;br /&gt;
    local dt = 0.1  -- time step in seconds&lt;br /&gt;
&lt;br /&gt;
    -- For neutral, update RPM based on throttle.&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        updateEngineRPMNeutral()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Calculate forces.&lt;br /&gt;
    local C_rr = (Engine.gear == 0) and C_rr_neutral or C_rr_inGear&lt;br /&gt;
    local F_roll = Vehicle.mass * G * C_rr&lt;br /&gt;
    local engineForce = getEngineForce()&lt;br /&gt;
    local dragForce = getAerodynamicDrag(math.abs(Vehicle.speed))&lt;br /&gt;
    local netForce = engineForce - (F_roll + dragForce)&lt;br /&gt;
&lt;br /&gt;
    -- Apply braking force if brakes are engaged.&lt;br /&gt;
    if Vehicle.brake then&lt;br /&gt;
        netForce = netForce - BRAKE_FORCE&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local acceleration = netForce / Vehicle.mass&lt;br /&gt;
    local vel = ll.GetVel()&lt;br /&gt;
    local horizontal_vec = vector(vel.x, vel.y, 0) &lt;br /&gt;
    Vehicle.speed = (ll.VecMag(horizontal_vec) + acceleration * dt)&lt;br /&gt;
&lt;br /&gt;
    -- Limit vehicle speed based on gear.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        if Vehicle.speed &amp;lt; 0 then &lt;br /&gt;
            Vehicle.speed = 0 &lt;br /&gt;
        end&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local maxSpeed = Engine.maxRPM / conversionFactor&lt;br /&gt;
        if Vehicle.speed &amp;gt; maxSpeed then&lt;br /&gt;
            Vehicle.speed = maxSpeed&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- When in gear, update RPM based on the new speed.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        Engine.rpm = computeEngineRPMFromSpeed()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Steering update using angular velocity and quaternions:&lt;br /&gt;
    local STEERING_RATE = math.rad(6)  -- desired steering rate per tick (radians)&lt;br /&gt;
    local yawDelta = Vehicle.steering * STEERING_RATE  -- small rotation for this tick&lt;br /&gt;
&lt;br /&gt;
    if yawDelta ~= 0 then&lt;br /&gt;
        local deltaQuat = ll.Euler2Rot(vector(0, 0, yawDelta))&lt;br /&gt;
        local angularSpeed = yawDelta / dt&lt;br /&gt;
        local angularVelocity = vector(0, 0, angularSpeed)&lt;br /&gt;
        ll.SetAngularVelocity(angularVelocity, 1)&lt;br /&gt;
        Vehicle.angle = Vehicle.angle + yawDelta&lt;br /&gt;
    else&lt;br /&gt;
        ll.SetAngularVelocity(vector(0, 0, 0), 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Display engine RPM and speed (converted to mph).&lt;br /&gt;
    local speed_mph = math.abs(Vehicle.speed) * 2.23694&lt;br /&gt;
    ll.SetText(&amp;quot;RPM: &amp;quot; .. math.floor(Engine.rpm) ..&lt;br /&gt;
               &amp;quot; | Speed: &amp;quot; .. string.format(&amp;quot;%.1f&amp;quot;, speed_mph) ..&lt;br /&gt;
               &amp;quot; mph&amp;quot;, vector(0,0,0), 1)&lt;br /&gt;
&lt;br /&gt;
    -- Only update velocity if the vehicle is near the ground, and we&#039;re in gear.&lt;br /&gt;
    if isVehicleOnGround() and Engine.gear ~= 0 then&lt;br /&gt;
        local velocityVector&lt;br /&gt;
        if Engine.gear &amp;gt; 0 then&lt;br /&gt;
            velocityVector = vector(Vehicle.speed, 0, 0)&lt;br /&gt;
        else&lt;br /&gt;
            -- Reverse gear&lt;br /&gt;
            velocityVector = vector(-Vehicle.speed, 0, 0)&lt;br /&gt;
        end&lt;br /&gt;
        ll.SetVelocity(velocityVector, 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Update the wheel rotations to visually match the steering.&lt;br /&gt;
    local maxWheelTurn = math.rad(30)  -- maximum wheel turn angle in radians&lt;br /&gt;
    local wheelAngle = -Vehicle.steering * maxWheelTurn&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_LEFT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_LEFT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(-4.71239, -wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_RIGHT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_RIGHT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(4.71239, wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched: starts engine and simulation.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    ll.Say(0, &amp;quot;Touch detected, requesting controls and starting engine.&amp;quot;)&lt;br /&gt;
    ll.RequestPermissions(ll.GetOwner(), PERMISSION_TAKE_CONTROLS)&lt;br /&gt;
    startEngine()&lt;br /&gt;
    findWheels()&lt;br /&gt;
    ll.SetTimerEvent(0.1)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when runtime permissions are granted.&lt;br /&gt;
function run_time_permissions(perm)&lt;br /&gt;
    if bit32.band(perm, PERMISSION_TAKE_CONTROLS) then&lt;br /&gt;
        ll.Say(0, &amp;quot;Permissions granted.&amp;quot;)&lt;br /&gt;
        local controlMask = bit32.bor(CONTROL_FWD, CONTROL_BACK, CONTROL_UP, CONTROL_DOWN, CONTROL_LEFT, CONTROL_RIGHT)&lt;br /&gt;
        ll.TakeControls(controlMask, 1, 1)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Handle control events:&lt;br /&gt;
-- • CONTROL_FWD applies throttle.&lt;br /&gt;
-- • CONTROL_BACK applies the brakes.&lt;br /&gt;
-- • CONTROL_UP and CONTROL_DOWN shift gears.&lt;br /&gt;
-- • CONTROL_LEFT and CONTROL_RIGHT steer the vehicle.&lt;br /&gt;
function control(avatar_id, level, edge)&lt;br /&gt;
    local start = bit32.band(level, edge)            &lt;br /&gt;
    local finish = bit32.band(bit32.bnot(level), edge) &lt;br /&gt;
    local held = bit32.band(level, bit32.bnot(edge))   &lt;br /&gt;
    local untouched = bit32.bnot(bit32.bor(level, edge)) &lt;br /&gt;
&lt;br /&gt;
    if bit32.band(held, CONTROL_BACK) ~= 0 then&lt;br /&gt;
        Vehicle.brake = true&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    elseif bit32.band(held, CONTROL_FWD) ~= 0 then&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 1.0&lt;br /&gt;
        if Engine.gear == 0 then&lt;br /&gt;
            updateEngineRPMNeutral()&lt;br /&gt;
        end&lt;br /&gt;
    else&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_UP) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear + 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_DOWN) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear - 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local steeringInput = 0&lt;br /&gt;
    if bit32.band(held, CONTROL_LEFT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput + 1&lt;br /&gt;
    end&lt;br /&gt;
    if bit32.band(held, CONTROL_RIGHT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput - 1&lt;br /&gt;
    end&lt;br /&gt;
    Vehicle.steering = steeringInput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== weather_box.lua ===&lt;br /&gt;
This script demonstrates coroutines, as well as working with a JSON-based web API:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local TEXTBOX_CHANNEL = 323242&lt;br /&gt;
-- kludge because we don&#039;t have LSL constants in here yet&lt;br /&gt;
local HTTP_BODY_MAXLENGTH = integer(2)&lt;br /&gt;
local PRIM_TEXT = integer(26)&lt;br /&gt;
local PRIM_SIZE = integer(7)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
gCityName = &amp;quot;&amp;quot;&lt;br /&gt;
gLatLong = nil  -- A table of 2 elems when it&#039;s populated&lt;br /&gt;
gCityRequestTask = nil&lt;br /&gt;
gWeatherRequestTask = nil&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- JSON stuff&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- From https://github.com/rxi/json.lua/blob/master/json.lua&lt;br /&gt;
&lt;br /&gt;
local json = {}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map = {&lt;br /&gt;
  [ &amp;quot;\\&amp;quot; ] = &amp;quot;\\&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\&amp;quot;&amp;quot; ] = &amp;quot;\&amp;quot;&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\b&amp;quot; ] = &amp;quot;b&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\f&amp;quot; ] = &amp;quot;f&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\n&amp;quot; ] = &amp;quot;n&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\r&amp;quot; ] = &amp;quot;r&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\t&amp;quot; ] = &amp;quot;t&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map_inv = { [ &amp;quot;/&amp;quot; ] = &amp;quot;/&amp;quot; }&lt;br /&gt;
for k, v in pairs(escape_char_map) do&lt;br /&gt;
  escape_char_map_inv[v] = k&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local parse&lt;br /&gt;
&lt;br /&gt;
local function create_set(...)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  for i = 1, select(&amp;quot;#&amp;quot;, ...) do&lt;br /&gt;
    res[ select(i, ...) ] = true&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local space_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;)&lt;br /&gt;
local delim_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, &amp;quot;]&amp;quot;, &amp;quot;}&amp;quot;, &amp;quot;,&amp;quot;)&lt;br /&gt;
local escape_chars  = create_set(&amp;quot;\\&amp;quot;, &amp;quot;/&amp;quot;, &#039;&amp;quot;&#039;, &amp;quot;b&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;r&amp;quot;, &amp;quot;t&amp;quot;, &amp;quot;u&amp;quot;)&lt;br /&gt;
local literals      = create_set(&amp;quot;true&amp;quot;, &amp;quot;false&amp;quot;, &amp;quot;null&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local literal_map = {&lt;br /&gt;
  [ &amp;quot;true&amp;quot;  ] = true,&lt;br /&gt;
  [ &amp;quot;false&amp;quot; ] = false,&lt;br /&gt;
  [ &amp;quot;null&amp;quot;  ] = nil,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function next_char(str, idx, set, negate)&lt;br /&gt;
  for i = idx, #str do&lt;br /&gt;
    if set[str:sub(i, i)] ~= negate then&lt;br /&gt;
      return i&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  return #str + 1&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function decode_error(str, idx, msg)&lt;br /&gt;
  local line_count = 1&lt;br /&gt;
  local col_count = 1&lt;br /&gt;
  for i = 1, idx - 1 do&lt;br /&gt;
    col_count = col_count + 1&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;\n&amp;quot; then&lt;br /&gt;
      line_count = line_count + 1&lt;br /&gt;
      col_count = 1&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;%s at line %d col %d&amp;quot;, msg, line_count, col_count) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function codepoint_to_utf8(n)&lt;br /&gt;
  -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&amp;amp;id=iws-appendixa&lt;br /&gt;
  local f = math.floor&lt;br /&gt;
  if n &amp;lt;= 0x7f then&lt;br /&gt;
    return string.char(n)&lt;br /&gt;
  elseif n &amp;lt;= 0x7ff then&lt;br /&gt;
    return string.char(f(n / 64) + 192, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0xffff then&lt;br /&gt;
    return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0x10ffff then&lt;br /&gt;
    return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,&lt;br /&gt;
                       f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;invalid unicode codepoint &#039;%x&#039;&amp;quot;, n) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_unicode_escape(s)&lt;br /&gt;
  local n1 = tonumber( s:sub(1, 4),  16 )&lt;br /&gt;
  local n2 = tonumber( s:sub(7, 10), 16 )&lt;br /&gt;
   -- Surrogate pair?&lt;br /&gt;
  if n2 then&lt;br /&gt;
    return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)&lt;br /&gt;
  else&lt;br /&gt;
    return codepoint_to_utf8(n1)&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_string(str, i)&lt;br /&gt;
  local res = &amp;quot;&amp;quot;&lt;br /&gt;
  local j = i + 1&lt;br /&gt;
  local k = j&lt;br /&gt;
&lt;br /&gt;
  while j &amp;lt;= #str do&lt;br /&gt;
    local x = str:byte(j)&lt;br /&gt;
&lt;br /&gt;
    if x &amp;lt; 32 then&lt;br /&gt;
      decode_error(str, j, &amp;quot;control character in string&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    elseif x == 92 then -- `\`: Escape&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      j = j + 1&lt;br /&gt;
      local c = str:sub(j, j)&lt;br /&gt;
      if c == &amp;quot;u&amp;quot; then&lt;br /&gt;
        local hex = str:match(&amp;quot;^[dD][89aAbB]%x%x\\u%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or str:match(&amp;quot;^%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or decode_error(str, j - 1, &amp;quot;invalid unicode escape in string&amp;quot;)&lt;br /&gt;
        res = res .. parse_unicode_escape(hex)&lt;br /&gt;
        j = j + #hex&lt;br /&gt;
      else&lt;br /&gt;
        if not escape_chars[c] then&lt;br /&gt;
          decode_error(str, j - 1, &amp;quot;invalid escape char &#039;&amp;quot; .. c .. &amp;quot;&#039; in string&amp;quot;)&lt;br /&gt;
        end&lt;br /&gt;
        res = res .. escape_char_map_inv[c]&lt;br /&gt;
      end&lt;br /&gt;
      k = j + 1&lt;br /&gt;
&lt;br /&gt;
    elseif x == 34 then -- `&amp;quot;`: End of string&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      return res, j + 1&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    j = j + 1&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  decode_error(str, i, &amp;quot;expected closing quote for string&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_number(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local s = str:sub(i, x - 1)&lt;br /&gt;
  local n = tonumber(s)&lt;br /&gt;
  if not n then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid number &#039;&amp;quot; .. s .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return n, x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_literal(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local word = str:sub(i, x - 1)&lt;br /&gt;
  if not literals[word] then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid literal &#039;&amp;quot; .. word .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return literal_map[word], x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_array(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  local n = 1&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local x&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of array?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;]&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read token&lt;br /&gt;
    x, i = parse(str, i)&lt;br /&gt;
    res[n] = x&lt;br /&gt;
    n = n + 1&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;]&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;]&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_object(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local key, val&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of object?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;}&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read key&lt;br /&gt;
    if str:sub(i, i) ~= &#039;&amp;quot;&#039; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected string for key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    key, i = parse(str, i)&lt;br /&gt;
    -- Read &#039;:&#039; delimiter&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    if str:sub(i, i) ~= &amp;quot;:&amp;quot; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected &#039;:&#039; after key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    i = next_char(str, i + 1, space_chars, true)&lt;br /&gt;
    -- Read value&lt;br /&gt;
    val, i = parse(str, i)&lt;br /&gt;
    -- Set&lt;br /&gt;
    res[key] = val&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;}&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;}&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local char_func_map = {&lt;br /&gt;
  [ &#039;&amp;quot;&#039; ] = parse_string,&lt;br /&gt;
  [ &amp;quot;0&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;1&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;2&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;3&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;4&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;5&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;6&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;7&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;8&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;9&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;-&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;t&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;f&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;n&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;[&amp;quot; ] = parse_array,&lt;br /&gt;
  [ &amp;quot;{&amp;quot; ] = parse_object,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
parse = function(str, idx)&lt;br /&gt;
  local chr = str:sub(idx, idx)&lt;br /&gt;
  local f = char_func_map[chr]&lt;br /&gt;
  if f then&lt;br /&gt;
    return f(str, idx)&lt;br /&gt;
  end&lt;br /&gt;
  decode_error(str, idx, &amp;quot;unexpected character &#039;&amp;quot; .. chr .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function json.decode(str)&lt;br /&gt;
  if type(str) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
    error(&amp;quot;expected argument of type string, got &amp;quot; .. type(str))&lt;br /&gt;
  end&lt;br /&gt;
  local res, idx = parse(str, next_char(str, 1, space_chars, true))&lt;br /&gt;
  idx = next_char(str, idx, space_chars, true)&lt;br /&gt;
  if idx &amp;lt;= #str then&lt;br /&gt;
    decode_error(str, idx, &amp;quot;trailing garbage&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Loop&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- A naive little event loop that lets you track running coroutines and dispatches&lt;br /&gt;
-- the events they&#039;re waiting for to them.&lt;br /&gt;
&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; awaited event&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    -- false has no semantic meaning here, we just want to reserve the space for&lt;br /&gt;
    -- the coroutine in the table without saying we&#039;re awaiting anything yet.&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- (internal) run the event&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    assert(coroutine.status(coro) ~= &amp;quot;dead&amp;quot;)&lt;br /&gt;
    -- assert(not self.running)&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    -- Since we&#039;re running, this isn&#039;t currently awaiting anything&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local success, ret = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    -- If we&#039;re not done then ret should be the kind of event we&#039;re awaiting.&lt;br /&gt;
    -- Otherwise it&#039;s an error message or something. Who cares.&lt;br /&gt;
    if not success then&lt;br /&gt;
        -- Get rid of the coro&lt;br /&gt;
        if ret then&lt;br /&gt;
            ll.OwnerSay(`Might have an error: {ret}`)&lt;br /&gt;
        end&lt;br /&gt;
        ret = nil&lt;br /&gt;
    end&lt;br /&gt;
    self._coros[coro] = ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Wake up any tasks that are waiting for this kind of event&lt;br /&gt;
function EventLoop:handle_event(name, ...)&lt;br /&gt;
    -- We may mutate self._coros, so do a clone&lt;br /&gt;
    local coros = table.clone(self._coros)&lt;br /&gt;
    ll.OwnerSay(`Handling {name}`)&lt;br /&gt;
&lt;br /&gt;
    for coro, awaited_event in coros do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            -- Hmm, this coroutine is dead, prune it.&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if awaited_event == name then&lt;br /&gt;
            ll.OwnerSay(`Dispatching {name} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_event_catcher(name)&lt;br /&gt;
    local function catcher(...)&lt;br /&gt;
        ll.OwnerSay(&amp;quot;yay&amp;quot;)&lt;br /&gt;
        EventLoop:handle_event(name, ...)&lt;br /&gt;
    end&lt;br /&gt;
    return catcher&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Helper function for waiting and telling the EventLoop what event you want&lt;br /&gt;
local function await_event(kind)&lt;br /&gt;
    -- You had better do this inside a running event loop&lt;br /&gt;
    assert(EventLoop.running)&lt;br /&gt;
    -- This will yield, the EventLoop will resume us once it has&lt;br /&gt;
    -- an event we might be interested in.&lt;br /&gt;
    return coroutine.yield(kind)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Script-specific functions&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
local function hide_children()&lt;br /&gt;
    for i=2,ll.GetNumberOfPrims() do&lt;br /&gt;
        -- Hide all the child prims&lt;br /&gt;
        ll.SetLinkAlpha(i, 0, -1)&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(i, {&lt;br /&gt;
            PRIM_SIZE, vector(0.5, 0.5, 0.5),&lt;br /&gt;
            PRIM_TEXT, &amp;quot;&amp;quot;, vector(1,1,1), 1&lt;br /&gt;
        })&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_textbox(avatar_id, message, channel)&lt;br /&gt;
    ll.TextBox(avatar_id, message, channel)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        local resp_channel, name, id, resp = await_event(&amp;quot;listen&amp;quot;)&lt;br /&gt;
        if id ~= avatar_id or channel ~= resp_channel then&lt;br /&gt;
            -- If this isn&#039;t a message we&#039;re interested in then wait for the next event&lt;br /&gt;
            ll.OwnerSay(`{id}, {avatar_id}, {resp_channel}, {channel}`)&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return resp&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function make_json_request(url)&lt;br /&gt;
    local req_id = ll.HTTPRequest(url, {HTTP_BODY_MAXLENGTH, integer(16000)}, &amp;quot;&amp;quot;)&lt;br /&gt;
    while true do&lt;br /&gt;
        local ev_id, status, metadata, body = await_event(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
        if ev_id ~= req_id then&lt;br /&gt;
            -- Not a response for the HTTP request we were waiting on, wait for the next event.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return status, metadata, body&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function request_weather()&lt;br /&gt;
    -- And you can use a wrapper like this to make an async function callable within both normal and coroutine contexts.&lt;br /&gt;
    -- Normally this sort of thing would not be necessary, but here it is since the EventLoop lives in our own code,&lt;br /&gt;
    -- and isn&#039;t a property of the actual script engine.&lt;br /&gt;
    if gWeatherRequestTask then&lt;br /&gt;
        EventLoop:kill_task(gWeatherRequestTask)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    gWeatherRequestTask = EventLoop:create_task(function()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Requesting weather&amp;quot;)&lt;br /&gt;
        local status, metadata, body = make_json_request(`https://api.open-meteo.com/v1/forecast?latitude={gLatLong[1]}&amp;amp;longitude={gLatLong[2]}&amp;amp;current=temperature_2m&amp;amp;daily=temperature_2m_max`)&lt;br /&gt;
        if status ~= 200 then&lt;br /&gt;
            ll.OwnerSay(&amp;quot;Error requesting weather&amp;quot;)&lt;br /&gt;
            return&lt;br /&gt;
        end&lt;br /&gt;
        local parsed = json.decode(body)&lt;br /&gt;
&lt;br /&gt;
        local current = parsed[&amp;quot;current&amp;quot;]&lt;br /&gt;
        ll.OwnerSay(`Temperature is {current[&amp;quot;temperature_2m&amp;quot;]}C`)&lt;br /&gt;
        ll.SetText(`Temperature in {gCityName} is currently {current[&amp;quot;temperature_2m&amp;quot;]}C`, vector(1,1,1), 1)&lt;br /&gt;
&lt;br /&gt;
        -- Show the highs for each day&lt;br /&gt;
        local days = parsed[&amp;quot;daily&amp;quot;][&amp;quot;temperature_2m_max&amp;quot;]&lt;br /&gt;
        for i=1,#days do&lt;br /&gt;
            local link_num = i + 1&lt;br /&gt;
            -- Higher temps = bigger on Z, negatives are unrepresentable :)&lt;br /&gt;
            local z_size = (days[i] / 30) * 2&lt;br /&gt;
            ll.SetLinkAlpha(link_num, 1, -1)&lt;br /&gt;
            ll.SetLinkPrimitiveParamsFast(link_num, {&lt;br /&gt;
                PRIM_SIZE, vector(0.5, 0.5, z_size),&lt;br /&gt;
                PRIM_TEXT, `{days[i]}C`, vector(1,1,1), 1&lt;br /&gt;
            })&lt;br /&gt;
        end&lt;br /&gt;
    end)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ask_for_city()&lt;br /&gt;
    local resp = send_textbox(ll.GetOwner(), &amp;quot;What city do you want the weather for?&amp;quot;, TEXTBOX_CHANNEL)&lt;br /&gt;
    if not resp then&lt;br /&gt;
        ll.OwnerSay(&amp;quot;Got no response for city request.&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    ll.OwnerSay(`You want the weather for {resp}.`)&lt;br /&gt;
    gLatLong = nil&lt;br /&gt;
    gCityName = resp&lt;br /&gt;
&lt;br /&gt;
    hide_children()&lt;br /&gt;
&lt;br /&gt;
    -- request the lat and long for this city&lt;br /&gt;
    local status, metadata, body = make_json_request(`https://nominatim.openstreetmap.org/search.php?city={ll.EscapeURL(resp)}&amp;amp;format=jsonv2`)&lt;br /&gt;
    local parsed = json.decode(body)&lt;br /&gt;
    if not #parsed then&lt;br /&gt;
        ll.OwnerSay(`Got no results for {resp}!`)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local first_result = parsed[1]&lt;br /&gt;
    gLatLong = {first_result[&amp;quot;lat&amp;quot;], first_result[&amp;quot;lon&amp;quot;]}&lt;br /&gt;
    ll.OwnerSay(`City Lat: {gLatLong[1]}, Long: {gLatLong[2]}`)&lt;br /&gt;
&lt;br /&gt;
    request_weather()&lt;br /&gt;
&lt;br /&gt;
    -- request weather again in 3 mins&lt;br /&gt;
    ll.SetTimerEvent(180)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Handlers&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- If we don&#039;t care about conventionally dispatched events, we can just set the event handler to directly send&lt;br /&gt;
-- event data to the event loop.&lt;br /&gt;
http_response = EventLoop:create_event_catcher(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
listen = EventLoop:create_event_catcher(&amp;quot;listen&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- More conventional event handlers work too.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    for i=0,num-1 do&lt;br /&gt;
        if ll.GetOwner() ~= ll.DetectedKey(i) then&lt;br /&gt;
            -- Go to next loop if it wasn&#039;t the owner touching us.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if gCityRequestTask then&lt;br /&gt;
            EventLoop:kill_task(gCityRequestTask)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Spin up a task to ask the user for their city&lt;br /&gt;
        gCityRequestTask = EventLoop:create_task(ask_for_city)&lt;br /&gt;
        break&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function timer()&lt;br /&gt;
    request_weather()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Not strictly necessary, but I like having the main function have its own scope.&lt;br /&gt;
-- Rather than just have top-level logic.&lt;br /&gt;
local function main()&lt;br /&gt;
    ll.Listen(TEXTBOX_CHANNEL, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.SetText(&amp;quot;&amp;quot;, vector(1,1,1), 1)&lt;br /&gt;
    hide_children()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Weatherbox operational!&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
main()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218009</id>
		<title>Lua Alpha</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218009"/>
		<updated>2025-03-04T22:46:27Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Signal Linden moved page Luau Alpha to Luau&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{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.}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Second Life Luau Alpha =&lt;br /&gt;
&lt;br /&gt;
[[File:Luau.png|720px|thumb|right|Luau logo]]&lt;br /&gt;
&lt;br /&gt;
We&#039;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.&lt;br /&gt;
&lt;br /&gt;
== What is Luau? ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Why Luau? ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== How to Get Started with Luau in Second Life ==&lt;br /&gt;
&lt;br /&gt;
In order to play with Luau, you&#039;ll need to download our Lua project viewer, and log onto our Aditi beta grid.&lt;br /&gt;
&lt;br /&gt;
* Access the latest build of the Luau-enabled Second Life Viewer from [https://releasenotes.secondlife.com/viewer/7.1.12.13526902562.html here].&lt;br /&gt;
&lt;br /&gt;
Once you&#039;ve got the new viewer and have logged onto the beta grid, head over to these Luau-enabled regions:&lt;br /&gt;
* Luau Yardang&lt;br /&gt;
* Luau Tombolo&lt;br /&gt;
* Luau Mesa&lt;br /&gt;
* Luau Tideland&lt;br /&gt;
&lt;br /&gt;
When editing a script in the new Lua project viewer, you&#039;ll notice a new &#039;&#039;&#039;Compiler&#039;&#039;&#039; 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).&lt;br /&gt;
&lt;br /&gt;
[[File:Compiler_dropdown.png|Compiler selection dropdown]]&lt;br /&gt;
&lt;br /&gt;
Compiler drop-down options:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;LSL: Legacy (LSO2)&#039;&#039;&#039; - Scripts written in LSL, to be run on the old LSO2 VM&lt;br /&gt;
* &#039;&#039;&#039;LSL: Mono&#039;&#039;&#039;- Scripts written in LSL, to be run on the Mono VM&lt;br /&gt;
* &#039;&#039;&#039;Lua&#039;&#039;&#039; - Scripts written in Lua, to be run on the Luau VM&lt;br /&gt;
* &#039;&#039;&#039;LSL/Luau&#039;&#039;&#039;- Scripts written in LSL, to be run on the Luau VM&lt;br /&gt;
&lt;br /&gt;
=== Transitioning from LSL to Luau ===&lt;br /&gt;
* &#039;&#039;&#039;Function Namespacing:&#039;&#039;&#039;&lt;br /&gt;
** In Luau, Linden Lab functions have been moved under the &#039;&#039;&#039;ll&#039;&#039;&#039; namespace.&lt;br /&gt;
** For example:&lt;br /&gt;
*** &#039;&#039;llSay&#039;&#039; becomes &#039;&#039;ll.Say&#039;&#039;&lt;br /&gt;
*** &#039;&#039;llGetPos&#039;&#039; becomes &#039;&#039;ll.GetPos&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Constants:&#039;&#039;&#039;&lt;br /&gt;
** Constants like &#039;&#039;PERMISSION_TAKE_CONTROLS&#039;&#039; and &#039;&#039;PRIM_ROT_LOCAL&#039;&#039; are not predefined in Luau.&lt;br /&gt;
** You must specify these constants if you wish to reference them in your scripts.&lt;br /&gt;
&lt;br /&gt;
=== Luau Libraries ===&lt;br /&gt;
* &#039;&#039;&#039;Coroutines:&#039;&#039;&#039;&lt;br /&gt;
** Luau supports coroutines, allowing for cooperative multitasking within scripts.&lt;br /&gt;
** Key functions include:&lt;br /&gt;
*** &#039;&#039;coroutine.create&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.status&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.resume&#039;&#039;&lt;br /&gt;
** Refer to the [https://luau.org/library#coroutine-library coroutine library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Bitwise Operations:&#039;&#039;&#039;&lt;br /&gt;
** Luau includes a &#039;&#039;bit32&#039;&#039; library for bitwise operations, enabling more efficient data manipulation.&lt;br /&gt;
** Refer to the [https://luau.org/library#bit32-library bit32 library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Standard Library:&#039;&#039;&#039;&lt;br /&gt;
** Luau comes equipped with a standard library of functions designed to manipulate built-in data types.&lt;br /&gt;
** Explore the [https://luau.org/library Luau Standard Library] for a comprehensive list of available functions.&lt;br /&gt;
&lt;br /&gt;
== Feedback and Support ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== Example Scripts ==&lt;br /&gt;
&lt;br /&gt;
To help you get started, we&#039;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&#039;s desire!&lt;br /&gt;
&lt;br /&gt;
=== dialog.lua ===&lt;br /&gt;
This script demonstrates how one can interact with dialog menus.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Define the menu buttons and dialog message.&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local ToucherID = nil&lt;br /&gt;
local dialogChannel = nil&lt;br /&gt;
local listenHandle = nil&lt;br /&gt;
&lt;br /&gt;
-- This function is called when the script first starts.&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Get the object&#039;s key and compute a dialog channel number.&lt;br /&gt;
    local key = ll.GetKey()&lt;br /&gt;
    -- Extract the last 7 characters of the key and convert it from hex.&lt;br /&gt;
    dialogChannel = -1 - tonumber(string.sub(tostring(key), -7, -1), 16)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    ToucherID = ll.DetectedKey(0)&lt;br /&gt;
    -- If there is already a listen handle, then remove it&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
    end&lt;br /&gt;
    listenHandle = ll.Listen(dialogChannel, &amp;quot;&amp;quot;, ToucherID, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
    -- Set a 60-second timer for response.&lt;br /&gt;
    ll.SetTimerEvent(60.0)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when a dialog response is received.&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    if message == &amp;quot;-&amp;quot; then&lt;br /&gt;
        -- Redisplay the dialog if the &amp;quot;-&amp;quot; option is selected.&lt;br /&gt;
        ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Stop the timer, and stop the listening handler.&lt;br /&gt;
    ll.ListenRemove(listenHandle)&lt;br /&gt;
    ll.SetTimerEvent(0)&lt;br /&gt;
    -- Let the user know what they selected&lt;br /&gt;
    ll.Say(0, `You selected {message}`)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the timer expires.&lt;br /&gt;
function timer()&lt;br /&gt;
    -- Stop the timer and clean up the listener.&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.SetTimerEvent(0)&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
        ll.Whisper(0, &amp;quot;Sorry. You snooze; you lose.&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dialog_coroutine.lua ===&lt;br /&gt;
This script demonstrates how one could use coroutines to handle dialog responses, with multi-user support.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Minimal EventLoop&lt;br /&gt;
-------------------------&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; eventName it’s waiting for&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status(coro) ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Internal helper: resumes a coroutine&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local ok, eventAwaited = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    if not ok then&lt;br /&gt;
        ll.OwnerSay(`Coroutine error: {eventAwaited}`)&lt;br /&gt;
        self._coros[coro] = nil&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- If still alive, &#039;eventAwaited&#039; is the next event it wants&lt;br /&gt;
    self._coros[coro] = eventAwaited&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:handle_event(eventName, ...)&lt;br /&gt;
    ll.OwnerSay(`Handling event {eventName}`)&lt;br /&gt;
    local snapshot = table.clone(self._coros)&lt;br /&gt;
    for coro, waitingFor in pairs(snapshot) do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        elseif waitingFor == eventName then&lt;br /&gt;
            ll.OwnerSay(`Dispatching event {eventName} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Coroutines use this to yield until an event&lt;br /&gt;
local function await_event(name)&lt;br /&gt;
    assert(EventLoop.running, &amp;quot;await_event called outside a coroutine!&amp;quot;)&lt;br /&gt;
    return coroutine.yield(name)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Script Logic&lt;br /&gt;
-------------------------&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- Use the chat listener to feed the event-loop&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    -- We handle all &#039;listen&#039; events via the event-loop&lt;br /&gt;
    EventLoop:handle_event(`listen_{channel}`, channel, name, sender_id, message)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the script starts&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Seed math.random so each new script run doesn’t repeat the same channels&lt;br /&gt;
    math.randomseed(ll.GetUnixTime())&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Script started with random channels for each user.&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- A coroutine function for a single user&#039;s dialog flow&lt;br /&gt;
local function handle_dialog_for_user(userId)&lt;br /&gt;
    -- Use a random channel&lt;br /&gt;
    local channel = math.random(0x1, 0xFFFF)&lt;br /&gt;
    -- Create a listener for that channel&lt;br /&gt;
    local listenHandle = ll.Listen(channel, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        -- Show the user a dialog&lt;br /&gt;
        ll.Dialog(userId, dialogInfo, buttons, channel)&lt;br /&gt;
&lt;br /&gt;
        -- Wait for the next &#039;listen&#039; event (channel, name, sender_id, message)&lt;br /&gt;
        local c, n, sid, msg = await_event(`listen_{channel}`)&lt;br /&gt;
&lt;br /&gt;
        -- If this &amp;quot;listen&amp;quot; event isn&#039;t for our channel/user, ignore it&lt;br /&gt;
        if c == channel and sid == userId then&lt;br /&gt;
            -- If user pressed &amp;quot;-&amp;quot;, re-display the menu, else they picked a final color&lt;br /&gt;
            if msg ~= &amp;quot;-&amp;quot; then&lt;br /&gt;
                ll.Say(0, `{n} selected {msg}`)&lt;br /&gt;
                -- Now that they&#039;ve chosen something else, remove the listener and finish&lt;br /&gt;
                ll.ListenRemove(listenHandle)&lt;br /&gt;
                return&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    for i=0, num_detected-1 do&lt;br /&gt;
        local toucherId = ll.DetectedKey(i)&lt;br /&gt;
        -- Create a separate coroutine for each person who touches&lt;br /&gt;
        EventLoop:create_task(function()&lt;br /&gt;
            handle_dialog_for_user(toucherId)&lt;br /&gt;
        end)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Run state_entry on load:&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== vehicle.lua ===&lt;br /&gt;
This script demonstrates how one may create their own vehicle scripts, which don’t rely on the underlying LL vehicle system. &lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Control and permission constants&lt;br /&gt;
local PERMISSION_TAKE_CONTROLS = 0x4&lt;br /&gt;
local CONTROL_FWD       = 0x1&lt;br /&gt;
local CONTROL_BACK      = 0x2&lt;br /&gt;
local CONTROL_UP        = 0x10&lt;br /&gt;
local CONTROL_DOWN      = 0x20&lt;br /&gt;
local CONTROL_LEFT      = 0x4&lt;br /&gt;
local CONTROL_RIGHT     = 0x8&lt;br /&gt;
local PRIM_ROT_LOCAL    = integer(29)&lt;br /&gt;
&lt;br /&gt;
-- These two will hold the link numbers for left and right front wheels&lt;br /&gt;
local LINK_WHEEL_LEFT = 0&lt;br /&gt;
local LINK_WHEEL_RIGHT = 0&lt;br /&gt;
&lt;br /&gt;
-- Integra Type R Engine properties&lt;br /&gt;
local Engine = {&lt;br /&gt;
    rpm = 800,              -- current engine RPM (starts at idle)&lt;br /&gt;
    gear = 0,               -- 0 = neutral; -1 = reverse; 1-5 for forward gears&lt;br /&gt;
    idleRPM = 800,          -- idle RPM&lt;br /&gt;
    maxRPM = 8300,          -- redline&lt;br /&gt;
    throttle = 0.0,         -- throttle value (0.0 to 1.0)&lt;br /&gt;
    gearRatios = {          -- Integra Type R gear ratios&lt;br /&gt;
        [-1] = 3.727,       -- reverse gear ratio&lt;br /&gt;
        [0]  = 0,          -- neutral&lt;br /&gt;
        [1]  = 3.727,&lt;br /&gt;
        [2]  = 2.256,&lt;br /&gt;
        [3]  = 1.729,&lt;br /&gt;
        [4]  = 1.416,&lt;br /&gt;
        [5]  = 1.194&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FINAL_DRIVE = 4.1   -- Final drive ratio&lt;br /&gt;
local WHEEL_RADIUS = 0.3  -- in meters&lt;br /&gt;
&lt;br /&gt;
-- Vehicle properties (using realistic Integra Type R mass)&lt;br /&gt;
local Vehicle = {&lt;br /&gt;
    speed = 0,      -- speed in m/s; negative indicates reverse motion&lt;br /&gt;
    mass = 1200,    -- mass in kg&lt;br /&gt;
    angle = 0,      -- heading angle in radians&lt;br /&gt;
    steering = 0,   -- steering input: -1 (left), 0 (none), 1 (right)&lt;br /&gt;
    brake = false   -- flag indicating if brakes are applied&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Engine constants (using realistic Integra Type R values)&lt;br /&gt;
local MAX_TORQUE = 200   -- in Nm&lt;br /&gt;
&lt;br /&gt;
-- Brake force constant (for active braking)&lt;br /&gt;
local BRAKE_FORCE = 4000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Define a torque curve function:&lt;br /&gt;
local function torqueCurve(rpm)&lt;br /&gt;
    local peakRPM = 6000&lt;br /&gt;
    if rpm &amp;lt; peakRPM then&lt;br /&gt;
        return 0.8 + 0.2 * ((rpm - Engine.idleRPM) / (peakRPM - Engine.idleRPM))&lt;br /&gt;
    else&lt;br /&gt;
        local factor = 1 - 0.5 * ((rpm - peakRPM) / (Engine.maxRPM - peakRPM))&lt;br /&gt;
        return math.max(0.5, factor)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Engine braking force when throttle is zero in gear.&lt;br /&gt;
local ENGINE_BRAKE_FORCE = 2000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Friction and drag constants&lt;br /&gt;
local G = 9.81            -- gravity (m/s^2)&lt;br /&gt;
local C_rr_inGear = 0.015 -- rolling resistance coefficient in gear&lt;br /&gt;
local C_rr_neutral = 0.05 -- rolling resistance coefficient in neutral&lt;br /&gt;
&lt;br /&gt;
local airDensity = 1.225      -- kg/m^3&lt;br /&gt;
local dragCoefficient = 0.3   -- aerodynamic drag coefficient&lt;br /&gt;
local frontalArea = 2.2       -- in m^2&lt;br /&gt;
&lt;br /&gt;
local function getAerodynamicDrag(speed)&lt;br /&gt;
    return 0.5 * airDensity * dragCoefficient * frontalArea * speed * speed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Compute engine RPM from vehicle speed when in gear.&lt;br /&gt;
local function computeEngineRPMFromSpeed()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return Engine.rpm  -- In neutral, RPM is managed independently.&lt;br /&gt;
    else&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local rpm = math.abs(Vehicle.speed) * conversionFactor&lt;br /&gt;
        if rpm &amp;lt; Engine.idleRPM then rpm = Engine.idleRPM end&lt;br /&gt;
        return rpm&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- For neutral, update RPM toward a target based on throttle.&lt;br /&gt;
local function updateEngineRPMNeutral()&lt;br /&gt;
    local targetRPM = Engine.idleRPM + (Engine.maxRPM - Engine.idleRPM) * Engine.throttle&lt;br /&gt;
    local changeRate = 200  -- RPM per tick&lt;br /&gt;
    if Engine.rpm &amp;lt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.min(Engine.rpm + changeRate, targetRPM)&lt;br /&gt;
    elseif Engine.rpm &amp;gt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.max(Engine.rpm - changeRate, targetRPM)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Calculate the engine force delivered to the wheels.&lt;br /&gt;
local function getEngineForce()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return 0  -- In neutral, no drive force is transmitted.&lt;br /&gt;
    else&lt;br /&gt;
        if Engine.throttle == 0 then&lt;br /&gt;
            -- Apply engine braking when throttle is released.&lt;br /&gt;
            return -ENGINE_BRAKE_FORCE&lt;br /&gt;
        else&lt;br /&gt;
            local torque = Engine.throttle * MAX_TORQUE * torqueCurve(Engine.rpm)&lt;br /&gt;
            local force = (torque * Engine.gearRatios[Engine.gear] * FINAL_DRIVE) / WHEEL_RADIUS&lt;br /&gt;
            return force&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Starts the engine by setting it to idle RPM.&lt;br /&gt;
local function startEngine()&lt;br /&gt;
    Engine.rpm = Engine.idleRPM&lt;br /&gt;
    ll.Say(0, &amp;quot;Engine started. RPM: &amp;quot; .. Engine.rpm)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Shifts the gear if valid.&lt;br /&gt;
local function shiftGear(newGear)&lt;br /&gt;
    if Engine.gearRatios[newGear] then&lt;br /&gt;
        Engine.gear = newGear&lt;br /&gt;
        ll.Say(0, &amp;quot;Shifted to gear &amp;quot; .. newGear)&lt;br /&gt;
    else&lt;br /&gt;
        ll.Say(0, &amp;quot;Invalid gear: &amp;quot; .. newGear)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function findWheels()&lt;br /&gt;
    local total = ll.GetNumberOfPrims()&lt;br /&gt;
    for i = 1, total do&lt;br /&gt;
        local name = ll.GetLinkName(i)&lt;br /&gt;
        if name == &amp;quot;WHEEL_LF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_LEFT = i&lt;br /&gt;
        elseif name == &amp;quot;WHEEL_RF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_RIGHT = i&lt;br /&gt;
        end&lt;br /&gt;
    end &lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local castRayCounter = 0&lt;br /&gt;
local lastGrounded = false&lt;br /&gt;
&lt;br /&gt;
local function isVehicleOnGround()&lt;br /&gt;
    castRayCounter = castRayCounter + 1&lt;br /&gt;
    if castRayCounter &amp;gt;= 5 then&lt;br /&gt;
        castRayCounter = 0&lt;br /&gt;
        local pos = ll.GetPos()&lt;br /&gt;
        local rayDistance = 4.5  -- threshold distance (in meters) to consider as &amp;quot;grounded&amp;quot;&lt;br /&gt;
        local rayStart = pos&lt;br /&gt;
        local rayEnd = pos + vector(0, 0, -rayDistance)&lt;br /&gt;
        local hitResult = ll.CastRay(rayStart, rayEnd, {})&lt;br /&gt;
        if table.getn(hitResult) == 3 then&lt;br /&gt;
            lastGrounded = true&lt;br /&gt;
        else&lt;br /&gt;
            lastGrounded = false&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return lastGrounded&lt;br /&gt;
end &lt;br /&gt;
&lt;br /&gt;
-- Timer event: updates engine, vehicle physics, steering, and rotates the wheels based on steering.&lt;br /&gt;
function timer()&lt;br /&gt;
    local dt = 0.1  -- time step in seconds&lt;br /&gt;
&lt;br /&gt;
    -- For neutral, update RPM based on throttle.&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        updateEngineRPMNeutral()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Calculate forces.&lt;br /&gt;
    local C_rr = (Engine.gear == 0) and C_rr_neutral or C_rr_inGear&lt;br /&gt;
    local F_roll = Vehicle.mass * G * C_rr&lt;br /&gt;
    local engineForce = getEngineForce()&lt;br /&gt;
    local dragForce = getAerodynamicDrag(math.abs(Vehicle.speed))&lt;br /&gt;
    local netForce = engineForce - (F_roll + dragForce)&lt;br /&gt;
&lt;br /&gt;
    -- Apply braking force if brakes are engaged.&lt;br /&gt;
    if Vehicle.brake then&lt;br /&gt;
        netForce = netForce - BRAKE_FORCE&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local acceleration = netForce / Vehicle.mass&lt;br /&gt;
    local vel = ll.GetVel()&lt;br /&gt;
    local horizontal_vec = vector(vel.x, vel.y, 0) &lt;br /&gt;
    Vehicle.speed = (ll.VecMag(horizontal_vec) + acceleration * dt)&lt;br /&gt;
&lt;br /&gt;
    -- Limit vehicle speed based on gear.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        if Vehicle.speed &amp;lt; 0 then &lt;br /&gt;
            Vehicle.speed = 0 &lt;br /&gt;
        end&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local maxSpeed = Engine.maxRPM / conversionFactor&lt;br /&gt;
        if Vehicle.speed &amp;gt; maxSpeed then&lt;br /&gt;
            Vehicle.speed = maxSpeed&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- When in gear, update RPM based on the new speed.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        Engine.rpm = computeEngineRPMFromSpeed()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Steering update using angular velocity and quaternions:&lt;br /&gt;
    local STEERING_RATE = math.rad(6)  -- desired steering rate per tick (radians)&lt;br /&gt;
    local yawDelta = Vehicle.steering * STEERING_RATE  -- small rotation for this tick&lt;br /&gt;
&lt;br /&gt;
    if yawDelta ~= 0 then&lt;br /&gt;
        local deltaQuat = ll.Euler2Rot(vector(0, 0, yawDelta))&lt;br /&gt;
        local angularSpeed = yawDelta / dt&lt;br /&gt;
        local angularVelocity = vector(0, 0, angularSpeed)&lt;br /&gt;
        ll.SetAngularVelocity(angularVelocity, 1)&lt;br /&gt;
        Vehicle.angle = Vehicle.angle + yawDelta&lt;br /&gt;
    else&lt;br /&gt;
        ll.SetAngularVelocity(vector(0, 0, 0), 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Display engine RPM and speed (converted to mph).&lt;br /&gt;
    local speed_mph = math.abs(Vehicle.speed) * 2.23694&lt;br /&gt;
    ll.SetText(&amp;quot;RPM: &amp;quot; .. math.floor(Engine.rpm) ..&lt;br /&gt;
               &amp;quot; | Speed: &amp;quot; .. string.format(&amp;quot;%.1f&amp;quot;, speed_mph) ..&lt;br /&gt;
               &amp;quot; mph&amp;quot;, vector(0,0,0), 1)&lt;br /&gt;
&lt;br /&gt;
    -- Only update velocity if the vehicle is near the ground, and we&#039;re in gear.&lt;br /&gt;
    if isVehicleOnGround() and Engine.gear ~= 0 then&lt;br /&gt;
        local velocityVector&lt;br /&gt;
        if Engine.gear &amp;gt; 0 then&lt;br /&gt;
            velocityVector = vector(Vehicle.speed, 0, 0)&lt;br /&gt;
        else&lt;br /&gt;
            -- Reverse gear&lt;br /&gt;
            velocityVector = vector(-Vehicle.speed, 0, 0)&lt;br /&gt;
        end&lt;br /&gt;
        ll.SetVelocity(velocityVector, 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Update the wheel rotations to visually match the steering.&lt;br /&gt;
    local maxWheelTurn = math.rad(30)  -- maximum wheel turn angle in radians&lt;br /&gt;
    local wheelAngle = -Vehicle.steering * maxWheelTurn&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_LEFT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_LEFT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(-4.71239, -wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_RIGHT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_RIGHT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(4.71239, wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched: starts engine and simulation.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    ll.Say(0, &amp;quot;Touch detected, requesting controls and starting engine.&amp;quot;)&lt;br /&gt;
    ll.RequestPermissions(ll.GetOwner(), PERMISSION_TAKE_CONTROLS)&lt;br /&gt;
    startEngine()&lt;br /&gt;
    findWheels()&lt;br /&gt;
    ll.SetTimerEvent(0.1)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when runtime permissions are granted.&lt;br /&gt;
function run_time_permissions(perm)&lt;br /&gt;
    if bit32.band(perm, PERMISSION_TAKE_CONTROLS) then&lt;br /&gt;
        ll.Say(0, &amp;quot;Permissions granted.&amp;quot;)&lt;br /&gt;
        local controlMask = bit32.bor(CONTROL_FWD, CONTROL_BACK, CONTROL_UP, CONTROL_DOWN, CONTROL_LEFT, CONTROL_RIGHT)&lt;br /&gt;
        ll.TakeControls(controlMask, 1, 1)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Handle control events:&lt;br /&gt;
-- • CONTROL_FWD applies throttle.&lt;br /&gt;
-- • CONTROL_BACK applies the brakes.&lt;br /&gt;
-- • CONTROL_UP and CONTROL_DOWN shift gears.&lt;br /&gt;
-- • CONTROL_LEFT and CONTROL_RIGHT steer the vehicle.&lt;br /&gt;
function control(avatar_id, level, edge)&lt;br /&gt;
    local start = bit32.band(level, edge)            &lt;br /&gt;
    local finish = bit32.band(bit32.bnot(level), edge) &lt;br /&gt;
    local held = bit32.band(level, bit32.bnot(edge))   &lt;br /&gt;
    local untouched = bit32.bnot(bit32.bor(level, edge)) &lt;br /&gt;
&lt;br /&gt;
    if bit32.band(held, CONTROL_BACK) ~= 0 then&lt;br /&gt;
        Vehicle.brake = true&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    elseif bit32.band(held, CONTROL_FWD) ~= 0 then&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 1.0&lt;br /&gt;
        if Engine.gear == 0 then&lt;br /&gt;
            updateEngineRPMNeutral()&lt;br /&gt;
        end&lt;br /&gt;
    else&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_UP) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear + 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_DOWN) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear - 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local steeringInput = 0&lt;br /&gt;
    if bit32.band(held, CONTROL_LEFT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput + 1&lt;br /&gt;
    end&lt;br /&gt;
    if bit32.band(held, CONTROL_RIGHT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput - 1&lt;br /&gt;
    end&lt;br /&gt;
    Vehicle.steering = steeringInput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== weather_box.lua ===&lt;br /&gt;
This script demonstrates coroutines, as well as working with a JSON-based web API:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local TEXTBOX_CHANNEL = 323242&lt;br /&gt;
-- kludge because we don&#039;t have LSL constants in here yet&lt;br /&gt;
local HTTP_BODY_MAXLENGTH = integer(2)&lt;br /&gt;
local PRIM_TEXT = integer(26)&lt;br /&gt;
local PRIM_SIZE = integer(7)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
gCityName = &amp;quot;&amp;quot;&lt;br /&gt;
gLatLong = nil  -- A table of 2 elems when it&#039;s populated&lt;br /&gt;
gCityRequestTask = nil&lt;br /&gt;
gWeatherRequestTask = nil&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- JSON stuff&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- From https://github.com/rxi/json.lua/blob/master/json.lua&lt;br /&gt;
&lt;br /&gt;
local json = {}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map = {&lt;br /&gt;
  [ &amp;quot;\\&amp;quot; ] = &amp;quot;\\&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\&amp;quot;&amp;quot; ] = &amp;quot;\&amp;quot;&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\b&amp;quot; ] = &amp;quot;b&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\f&amp;quot; ] = &amp;quot;f&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\n&amp;quot; ] = &amp;quot;n&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\r&amp;quot; ] = &amp;quot;r&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\t&amp;quot; ] = &amp;quot;t&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map_inv = { [ &amp;quot;/&amp;quot; ] = &amp;quot;/&amp;quot; }&lt;br /&gt;
for k, v in pairs(escape_char_map) do&lt;br /&gt;
  escape_char_map_inv[v] = k&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local parse&lt;br /&gt;
&lt;br /&gt;
local function create_set(...)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  for i = 1, select(&amp;quot;#&amp;quot;, ...) do&lt;br /&gt;
    res[ select(i, ...) ] = true&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local space_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;)&lt;br /&gt;
local delim_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, &amp;quot;]&amp;quot;, &amp;quot;}&amp;quot;, &amp;quot;,&amp;quot;)&lt;br /&gt;
local escape_chars  = create_set(&amp;quot;\\&amp;quot;, &amp;quot;/&amp;quot;, &#039;&amp;quot;&#039;, &amp;quot;b&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;r&amp;quot;, &amp;quot;t&amp;quot;, &amp;quot;u&amp;quot;)&lt;br /&gt;
local literals      = create_set(&amp;quot;true&amp;quot;, &amp;quot;false&amp;quot;, &amp;quot;null&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local literal_map = {&lt;br /&gt;
  [ &amp;quot;true&amp;quot;  ] = true,&lt;br /&gt;
  [ &amp;quot;false&amp;quot; ] = false,&lt;br /&gt;
  [ &amp;quot;null&amp;quot;  ] = nil,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function next_char(str, idx, set, negate)&lt;br /&gt;
  for i = idx, #str do&lt;br /&gt;
    if set[str:sub(i, i)] ~= negate then&lt;br /&gt;
      return i&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  return #str + 1&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function decode_error(str, idx, msg)&lt;br /&gt;
  local line_count = 1&lt;br /&gt;
  local col_count = 1&lt;br /&gt;
  for i = 1, idx - 1 do&lt;br /&gt;
    col_count = col_count + 1&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;\n&amp;quot; then&lt;br /&gt;
      line_count = line_count + 1&lt;br /&gt;
      col_count = 1&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;%s at line %d col %d&amp;quot;, msg, line_count, col_count) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function codepoint_to_utf8(n)&lt;br /&gt;
  -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&amp;amp;id=iws-appendixa&lt;br /&gt;
  local f = math.floor&lt;br /&gt;
  if n &amp;lt;= 0x7f then&lt;br /&gt;
    return string.char(n)&lt;br /&gt;
  elseif n &amp;lt;= 0x7ff then&lt;br /&gt;
    return string.char(f(n / 64) + 192, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0xffff then&lt;br /&gt;
    return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0x10ffff then&lt;br /&gt;
    return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,&lt;br /&gt;
                       f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;invalid unicode codepoint &#039;%x&#039;&amp;quot;, n) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_unicode_escape(s)&lt;br /&gt;
  local n1 = tonumber( s:sub(1, 4),  16 )&lt;br /&gt;
  local n2 = tonumber( s:sub(7, 10), 16 )&lt;br /&gt;
   -- Surrogate pair?&lt;br /&gt;
  if n2 then&lt;br /&gt;
    return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)&lt;br /&gt;
  else&lt;br /&gt;
    return codepoint_to_utf8(n1)&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_string(str, i)&lt;br /&gt;
  local res = &amp;quot;&amp;quot;&lt;br /&gt;
  local j = i + 1&lt;br /&gt;
  local k = j&lt;br /&gt;
&lt;br /&gt;
  while j &amp;lt;= #str do&lt;br /&gt;
    local x = str:byte(j)&lt;br /&gt;
&lt;br /&gt;
    if x &amp;lt; 32 then&lt;br /&gt;
      decode_error(str, j, &amp;quot;control character in string&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    elseif x == 92 then -- `\`: Escape&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      j = j + 1&lt;br /&gt;
      local c = str:sub(j, j)&lt;br /&gt;
      if c == &amp;quot;u&amp;quot; then&lt;br /&gt;
        local hex = str:match(&amp;quot;^[dD][89aAbB]%x%x\\u%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or str:match(&amp;quot;^%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or decode_error(str, j - 1, &amp;quot;invalid unicode escape in string&amp;quot;)&lt;br /&gt;
        res = res .. parse_unicode_escape(hex)&lt;br /&gt;
        j = j + #hex&lt;br /&gt;
      else&lt;br /&gt;
        if not escape_chars[c] then&lt;br /&gt;
          decode_error(str, j - 1, &amp;quot;invalid escape char &#039;&amp;quot; .. c .. &amp;quot;&#039; in string&amp;quot;)&lt;br /&gt;
        end&lt;br /&gt;
        res = res .. escape_char_map_inv[c]&lt;br /&gt;
      end&lt;br /&gt;
      k = j + 1&lt;br /&gt;
&lt;br /&gt;
    elseif x == 34 then -- `&amp;quot;`: End of string&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      return res, j + 1&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    j = j + 1&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  decode_error(str, i, &amp;quot;expected closing quote for string&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_number(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local s = str:sub(i, x - 1)&lt;br /&gt;
  local n = tonumber(s)&lt;br /&gt;
  if not n then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid number &#039;&amp;quot; .. s .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return n, x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_literal(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local word = str:sub(i, x - 1)&lt;br /&gt;
  if not literals[word] then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid literal &#039;&amp;quot; .. word .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return literal_map[word], x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_array(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  local n = 1&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local x&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of array?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;]&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read token&lt;br /&gt;
    x, i = parse(str, i)&lt;br /&gt;
    res[n] = x&lt;br /&gt;
    n = n + 1&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;]&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;]&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_object(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local key, val&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of object?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;}&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read key&lt;br /&gt;
    if str:sub(i, i) ~= &#039;&amp;quot;&#039; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected string for key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    key, i = parse(str, i)&lt;br /&gt;
    -- Read &#039;:&#039; delimiter&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    if str:sub(i, i) ~= &amp;quot;:&amp;quot; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected &#039;:&#039; after key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    i = next_char(str, i + 1, space_chars, true)&lt;br /&gt;
    -- Read value&lt;br /&gt;
    val, i = parse(str, i)&lt;br /&gt;
    -- Set&lt;br /&gt;
    res[key] = val&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;}&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;}&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local char_func_map = {&lt;br /&gt;
  [ &#039;&amp;quot;&#039; ] = parse_string,&lt;br /&gt;
  [ &amp;quot;0&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;1&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;2&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;3&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;4&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;5&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;6&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;7&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;8&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;9&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;-&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;t&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;f&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;n&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;[&amp;quot; ] = parse_array,&lt;br /&gt;
  [ &amp;quot;{&amp;quot; ] = parse_object,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
parse = function(str, idx)&lt;br /&gt;
  local chr = str:sub(idx, idx)&lt;br /&gt;
  local f = char_func_map[chr]&lt;br /&gt;
  if f then&lt;br /&gt;
    return f(str, idx)&lt;br /&gt;
  end&lt;br /&gt;
  decode_error(str, idx, &amp;quot;unexpected character &#039;&amp;quot; .. chr .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function json.decode(str)&lt;br /&gt;
  if type(str) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
    error(&amp;quot;expected argument of type string, got &amp;quot; .. type(str))&lt;br /&gt;
  end&lt;br /&gt;
  local res, idx = parse(str, next_char(str, 1, space_chars, true))&lt;br /&gt;
  idx = next_char(str, idx, space_chars, true)&lt;br /&gt;
  if idx &amp;lt;= #str then&lt;br /&gt;
    decode_error(str, idx, &amp;quot;trailing garbage&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Loop&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- A naive little event loop that lets you track running coroutines and dispatches&lt;br /&gt;
-- the events they&#039;re waiting for to them.&lt;br /&gt;
&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; awaited event&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    -- false has no semantic meaning here, we just want to reserve the space for&lt;br /&gt;
    -- the coroutine in the table without saying we&#039;re awaiting anything yet.&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- (internal) run the event&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    assert(coroutine.status(coro) ~= &amp;quot;dead&amp;quot;)&lt;br /&gt;
    -- assert(not self.running)&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    -- Since we&#039;re running, this isn&#039;t currently awaiting anything&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local success, ret = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    -- If we&#039;re not done then ret should be the kind of event we&#039;re awaiting.&lt;br /&gt;
    -- Otherwise it&#039;s an error message or something. Who cares.&lt;br /&gt;
    if not success then&lt;br /&gt;
        -- Get rid of the coro&lt;br /&gt;
        if ret then&lt;br /&gt;
            ll.OwnerSay(`Might have an error: {ret}`)&lt;br /&gt;
        end&lt;br /&gt;
        ret = nil&lt;br /&gt;
    end&lt;br /&gt;
    self._coros[coro] = ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Wake up any tasks that are waiting for this kind of event&lt;br /&gt;
function EventLoop:handle_event(name, ...)&lt;br /&gt;
    -- We may mutate self._coros, so do a clone&lt;br /&gt;
    local coros = table.clone(self._coros)&lt;br /&gt;
    ll.OwnerSay(`Handling {name}`)&lt;br /&gt;
&lt;br /&gt;
    for coro, awaited_event in coros do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            -- Hmm, this coroutine is dead, prune it.&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if awaited_event == name then&lt;br /&gt;
            ll.OwnerSay(`Dispatching {name} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_event_catcher(name)&lt;br /&gt;
    local function catcher(...)&lt;br /&gt;
        ll.OwnerSay(&amp;quot;yay&amp;quot;)&lt;br /&gt;
        EventLoop:handle_event(name, ...)&lt;br /&gt;
    end&lt;br /&gt;
    return catcher&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Helper function for waiting and telling the EventLoop what event you want&lt;br /&gt;
local function await_event(kind)&lt;br /&gt;
    -- You had better do this inside a running event loop&lt;br /&gt;
    assert(EventLoop.running)&lt;br /&gt;
    -- This will yield, the EventLoop will resume us once it has&lt;br /&gt;
    -- an event we might be interested in.&lt;br /&gt;
    return coroutine.yield(kind)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Script-specific functions&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
local function hide_children()&lt;br /&gt;
    for i=2,ll.GetNumberOfPrims() do&lt;br /&gt;
        -- Hide all the child prims&lt;br /&gt;
        ll.SetLinkAlpha(i, 0, -1)&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(i, {&lt;br /&gt;
            PRIM_SIZE, vector(0.5, 0.5, 0.5),&lt;br /&gt;
            PRIM_TEXT, &amp;quot;&amp;quot;, vector(1,1,1), 1&lt;br /&gt;
        })&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_textbox(avatar_id, message, channel)&lt;br /&gt;
    ll.TextBox(avatar_id, message, channel)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        local resp_channel, name, id, resp = await_event(&amp;quot;listen&amp;quot;)&lt;br /&gt;
        if id ~= avatar_id or channel ~= resp_channel then&lt;br /&gt;
            -- If this isn&#039;t a message we&#039;re interested in then wait for the next event&lt;br /&gt;
            ll.OwnerSay(`{id}, {avatar_id}, {resp_channel}, {channel}`)&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return resp&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function make_json_request(url)&lt;br /&gt;
    local req_id = ll.HTTPRequest(url, {HTTP_BODY_MAXLENGTH, integer(16000)}, &amp;quot;&amp;quot;)&lt;br /&gt;
    while true do&lt;br /&gt;
        local ev_id, status, metadata, body = await_event(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
        if ev_id ~= req_id then&lt;br /&gt;
            -- Not a response for the HTTP request we were waiting on, wait for the next event.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return status, metadata, body&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function request_weather()&lt;br /&gt;
    -- And you can use a wrapper like this to make an async function callable within both normal and coroutine contexts.&lt;br /&gt;
    -- Normally this sort of thing would not be necessary, but here it is since the EventLoop lives in our own code,&lt;br /&gt;
    -- and isn&#039;t a property of the actual script engine.&lt;br /&gt;
    if gWeatherRequestTask then&lt;br /&gt;
        EventLoop:kill_task(gWeatherRequestTask)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    gWeatherRequestTask = EventLoop:create_task(function()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Requesting weather&amp;quot;)&lt;br /&gt;
        local status, metadata, body = make_json_request(`https://api.open-meteo.com/v1/forecast?latitude={gLatLong[1]}&amp;amp;longitude={gLatLong[2]}&amp;amp;current=temperature_2m&amp;amp;daily=temperature_2m_max`)&lt;br /&gt;
        if status ~= 200 then&lt;br /&gt;
            ll.OwnerSay(&amp;quot;Error requesting weather&amp;quot;)&lt;br /&gt;
            return&lt;br /&gt;
        end&lt;br /&gt;
        local parsed = json.decode(body)&lt;br /&gt;
&lt;br /&gt;
        local current = parsed[&amp;quot;current&amp;quot;]&lt;br /&gt;
        ll.OwnerSay(`Temperature is {current[&amp;quot;temperature_2m&amp;quot;]}C`)&lt;br /&gt;
        ll.SetText(`Temperature in {gCityName} is currently {current[&amp;quot;temperature_2m&amp;quot;]}C`, vector(1,1,1), 1)&lt;br /&gt;
&lt;br /&gt;
        -- Show the highs for each day&lt;br /&gt;
        local days = parsed[&amp;quot;daily&amp;quot;][&amp;quot;temperature_2m_max&amp;quot;]&lt;br /&gt;
        for i=1,#days do&lt;br /&gt;
            local link_num = i + 1&lt;br /&gt;
            -- Higher temps = bigger on Z, negatives are unrepresentable :)&lt;br /&gt;
            local z_size = (days[i] / 30) * 2&lt;br /&gt;
            ll.SetLinkAlpha(link_num, 1, -1)&lt;br /&gt;
            ll.SetLinkPrimitiveParamsFast(link_num, {&lt;br /&gt;
                PRIM_SIZE, vector(0.5, 0.5, z_size),&lt;br /&gt;
                PRIM_TEXT, `{days[i]}C`, vector(1,1,1), 1&lt;br /&gt;
            })&lt;br /&gt;
        end&lt;br /&gt;
    end)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ask_for_city()&lt;br /&gt;
    local resp = send_textbox(ll.GetOwner(), &amp;quot;What city do you want the weather for?&amp;quot;, TEXTBOX_CHANNEL)&lt;br /&gt;
    if not resp then&lt;br /&gt;
        ll.OwnerSay(&amp;quot;Got no response for city request.&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    ll.OwnerSay(`You want the weather for {resp}.`)&lt;br /&gt;
    gLatLong = nil&lt;br /&gt;
    gCityName = resp&lt;br /&gt;
&lt;br /&gt;
    hide_children()&lt;br /&gt;
&lt;br /&gt;
    -- request the lat and long for this city&lt;br /&gt;
    local status, metadata, body = make_json_request(`https://nominatim.openstreetmap.org/search.php?city={ll.EscapeURL(resp)}&amp;amp;format=jsonv2`)&lt;br /&gt;
    local parsed = json.decode(body)&lt;br /&gt;
    if not #parsed then&lt;br /&gt;
        ll.OwnerSay(`Got no results for {resp}!`)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local first_result = parsed[1]&lt;br /&gt;
    gLatLong = {first_result[&amp;quot;lat&amp;quot;], first_result[&amp;quot;lon&amp;quot;]}&lt;br /&gt;
    ll.OwnerSay(`City Lat: {gLatLong[1]}, Long: {gLatLong[2]}`)&lt;br /&gt;
&lt;br /&gt;
    request_weather()&lt;br /&gt;
&lt;br /&gt;
    -- request weather again in 3 mins&lt;br /&gt;
    ll.SetTimerEvent(180)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Handlers&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- If we don&#039;t care about conventionally dispatched events, we can just set the event handler to directly send&lt;br /&gt;
-- event data to the event loop.&lt;br /&gt;
http_response = EventLoop:create_event_catcher(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
listen = EventLoop:create_event_catcher(&amp;quot;listen&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- More conventional event handlers work too.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    for i=0,num-1 do&lt;br /&gt;
        if ll.GetOwner() ~= ll.DetectedKey(i) then&lt;br /&gt;
            -- Go to next loop if it wasn&#039;t the owner touching us.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if gCityRequestTask then&lt;br /&gt;
            EventLoop:kill_task(gCityRequestTask)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Spin up a task to ask the user for their city&lt;br /&gt;
        gCityRequestTask = EventLoop:create_task(ask_for_city)&lt;br /&gt;
        break&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function timer()&lt;br /&gt;
    request_weather()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Not strictly necessary, but I like having the main function have its own scope.&lt;br /&gt;
-- Rather than just have top-level logic.&lt;br /&gt;
local function main()&lt;br /&gt;
    ll.Listen(TEXTBOX_CHANNEL, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.SetText(&amp;quot;&amp;quot;, vector(1,1,1), 1)&lt;br /&gt;
    hide_children()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Weatherbox operational!&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
main()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218008</id>
		<title>Lua Alpha</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218008"/>
		<updated>2025-03-04T22:44:09Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{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.}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Second Life Luau Alpha =&lt;br /&gt;
&lt;br /&gt;
[[File:Luau.png|720px|thumb|right|Luau logo]]&lt;br /&gt;
&lt;br /&gt;
We&#039;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.&lt;br /&gt;
&lt;br /&gt;
== What is Luau? ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Why Luau? ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== How to Get Started with Luau in Second Life ==&lt;br /&gt;
&lt;br /&gt;
In order to play with Luau, you&#039;ll need to download our Lua project viewer, and log onto our Aditi beta grid.&lt;br /&gt;
&lt;br /&gt;
* Access the latest build of the Luau-enabled Second Life Viewer from [https://releasenotes.secondlife.com/viewer/7.1.12.13526902562.html here].&lt;br /&gt;
&lt;br /&gt;
Once you&#039;ve got the new viewer and have logged onto the beta grid, head over to these Luau-enabled regions:&lt;br /&gt;
* Luau Yardang&lt;br /&gt;
* Luau Tombolo&lt;br /&gt;
* Luau Mesa&lt;br /&gt;
* Luau Tideland&lt;br /&gt;
&lt;br /&gt;
When editing a script in the new Lua project viewer, you&#039;ll notice a new &#039;&#039;&#039;Compiler&#039;&#039;&#039; 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).&lt;br /&gt;
&lt;br /&gt;
[[File:Compiler_dropdown.png|Compiler selection dropdown]]&lt;br /&gt;
&lt;br /&gt;
Compiler drop-down options:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;LSL: Legacy (LSO2)&#039;&#039;&#039; - Scripts written in LSL, to be run on the old LSO2 VM&lt;br /&gt;
* &#039;&#039;&#039;LSL: Mono&#039;&#039;&#039;- Scripts written in LSL, to be run on the Mono VM&lt;br /&gt;
* &#039;&#039;&#039;Lua&#039;&#039;&#039; - Scripts written in Lua, to be run on the Luau VM&lt;br /&gt;
* &#039;&#039;&#039;LSL/Luau&#039;&#039;&#039;- Scripts written in LSL, to be run on the Luau VM&lt;br /&gt;
&lt;br /&gt;
=== Transitioning from LSL to Luau ===&lt;br /&gt;
* &#039;&#039;&#039;Function Namespacing:&#039;&#039;&#039;&lt;br /&gt;
** In Luau, Linden Lab functions have been moved under the &#039;&#039;&#039;ll&#039;&#039;&#039; namespace.&lt;br /&gt;
** For example:&lt;br /&gt;
*** &#039;&#039;llSay&#039;&#039; becomes &#039;&#039;ll.Say&#039;&#039;&lt;br /&gt;
*** &#039;&#039;llGetPos&#039;&#039; becomes &#039;&#039;ll.GetPos&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Constants:&#039;&#039;&#039;&lt;br /&gt;
** Constants like &#039;&#039;PERMISSION_TAKE_CONTROLS&#039;&#039; and &#039;&#039;PRIM_ROT_LOCAL&#039;&#039; are not predefined in Luau.&lt;br /&gt;
** You must specify these constants if you wish to reference them in your scripts.&lt;br /&gt;
&lt;br /&gt;
=== Luau Libraries ===&lt;br /&gt;
* &#039;&#039;&#039;Coroutines:&#039;&#039;&#039;&lt;br /&gt;
** Luau supports coroutines, allowing for cooperative multitasking within scripts.&lt;br /&gt;
** Key functions include:&lt;br /&gt;
*** &#039;&#039;coroutine.create&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.status&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.resume&#039;&#039;&lt;br /&gt;
** Refer to the [https://luau.org/library#coroutine-library coroutine library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Bitwise Operations:&#039;&#039;&#039;&lt;br /&gt;
** Luau includes a &#039;&#039;bit32&#039;&#039; library for bitwise operations, enabling more efficient data manipulation.&lt;br /&gt;
** Refer to the [https://luau.org/library#bit32-library bit32 library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Standard Library:&#039;&#039;&#039;&lt;br /&gt;
** Luau comes equipped with a standard library of functions designed to manipulate built-in data types.&lt;br /&gt;
** Explore the [https://luau.org/library Luau Standard Library] for a comprehensive list of available functions.&lt;br /&gt;
&lt;br /&gt;
== Feedback and Support ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== Example Scripts ==&lt;br /&gt;
&lt;br /&gt;
To help you get started, we&#039;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&#039;s desire!&lt;br /&gt;
&lt;br /&gt;
=== dialog.lua ===&lt;br /&gt;
This script demonstrates how one can interact with dialog menus.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Define the menu buttons and dialog message.&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local ToucherID = nil&lt;br /&gt;
local dialogChannel = nil&lt;br /&gt;
local listenHandle = nil&lt;br /&gt;
&lt;br /&gt;
-- This function is called when the script first starts.&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Get the object&#039;s key and compute a dialog channel number.&lt;br /&gt;
    local key = ll.GetKey()&lt;br /&gt;
    -- Extract the last 7 characters of the key and convert it from hex.&lt;br /&gt;
    dialogChannel = -1 - tonumber(string.sub(tostring(key), -7, -1), 16)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    ToucherID = ll.DetectedKey(0)&lt;br /&gt;
    -- If there is already a listen handle, then remove it&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
    end&lt;br /&gt;
    listenHandle = ll.Listen(dialogChannel, &amp;quot;&amp;quot;, ToucherID, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
    -- Set a 60-second timer for response.&lt;br /&gt;
    ll.SetTimerEvent(60.0)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when a dialog response is received.&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    if message == &amp;quot;-&amp;quot; then&lt;br /&gt;
        -- Redisplay the dialog if the &amp;quot;-&amp;quot; option is selected.&lt;br /&gt;
        ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Stop the timer, and stop the listening handler.&lt;br /&gt;
    ll.ListenRemove(listenHandle)&lt;br /&gt;
    ll.SetTimerEvent(0)&lt;br /&gt;
    -- Let the user know what they selected&lt;br /&gt;
    ll.Say(0, `You selected {message}`)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the timer expires.&lt;br /&gt;
function timer()&lt;br /&gt;
    -- Stop the timer and clean up the listener.&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.SetTimerEvent(0)&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
        ll.Whisper(0, &amp;quot;Sorry. You snooze; you lose.&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dialog_coroutine.lua ===&lt;br /&gt;
This script demonstrates how one could use coroutines to handle dialog responses, with multi-user support.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Minimal EventLoop&lt;br /&gt;
-------------------------&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; eventName it’s waiting for&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status(coro) ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Internal helper: resumes a coroutine&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local ok, eventAwaited = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    if not ok then&lt;br /&gt;
        ll.OwnerSay(`Coroutine error: {eventAwaited}`)&lt;br /&gt;
        self._coros[coro] = nil&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- If still alive, &#039;eventAwaited&#039; is the next event it wants&lt;br /&gt;
    self._coros[coro] = eventAwaited&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:handle_event(eventName, ...)&lt;br /&gt;
    ll.OwnerSay(`Handling event {eventName}`)&lt;br /&gt;
    local snapshot = table.clone(self._coros)&lt;br /&gt;
    for coro, waitingFor in pairs(snapshot) do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        elseif waitingFor == eventName then&lt;br /&gt;
            ll.OwnerSay(`Dispatching event {eventName} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Coroutines use this to yield until an event&lt;br /&gt;
local function await_event(name)&lt;br /&gt;
    assert(EventLoop.running, &amp;quot;await_event called outside a coroutine!&amp;quot;)&lt;br /&gt;
    return coroutine.yield(name)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Script Logic&lt;br /&gt;
-------------------------&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- Use the chat listener to feed the event-loop&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    -- We handle all &#039;listen&#039; events via the event-loop&lt;br /&gt;
    EventLoop:handle_event(`listen_{channel}`, channel, name, sender_id, message)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the script starts&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Seed math.random so each new script run doesn’t repeat the same channels&lt;br /&gt;
    math.randomseed(ll.GetUnixTime())&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Script started with random channels for each user.&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- A coroutine function for a single user&#039;s dialog flow&lt;br /&gt;
local function handle_dialog_for_user(userId)&lt;br /&gt;
    -- Use a random channel&lt;br /&gt;
    local channel = math.random(0x1, 0xFFFF)&lt;br /&gt;
    -- Create a listener for that channel&lt;br /&gt;
    local listenHandle = ll.Listen(channel, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        -- Show the user a dialog&lt;br /&gt;
        ll.Dialog(userId, dialogInfo, buttons, channel)&lt;br /&gt;
&lt;br /&gt;
        -- Wait for the next &#039;listen&#039; event (channel, name, sender_id, message)&lt;br /&gt;
        local c, n, sid, msg = await_event(`listen_{channel}`)&lt;br /&gt;
&lt;br /&gt;
        -- If this &amp;quot;listen&amp;quot; event isn&#039;t for our channel/user, ignore it&lt;br /&gt;
        if c == channel and sid == userId then&lt;br /&gt;
            -- If user pressed &amp;quot;-&amp;quot;, re-display the menu, else they picked a final color&lt;br /&gt;
            if msg ~= &amp;quot;-&amp;quot; then&lt;br /&gt;
                ll.Say(0, `{n} selected {msg}`)&lt;br /&gt;
                -- Now that they&#039;ve chosen something else, remove the listener and finish&lt;br /&gt;
                ll.ListenRemove(listenHandle)&lt;br /&gt;
                return&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    for i=0, num_detected-1 do&lt;br /&gt;
        local toucherId = ll.DetectedKey(i)&lt;br /&gt;
        -- Create a separate coroutine for each person who touches&lt;br /&gt;
        EventLoop:create_task(function()&lt;br /&gt;
            handle_dialog_for_user(toucherId)&lt;br /&gt;
        end)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Run state_entry on load:&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== vehicle.lua ===&lt;br /&gt;
This script demonstrates how one may create their own vehicle scripts, which don’t rely on the underlying LL vehicle system. &lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Control and permission constants&lt;br /&gt;
local PERMISSION_TAKE_CONTROLS = 0x4&lt;br /&gt;
local CONTROL_FWD       = 0x1&lt;br /&gt;
local CONTROL_BACK      = 0x2&lt;br /&gt;
local CONTROL_UP        = 0x10&lt;br /&gt;
local CONTROL_DOWN      = 0x20&lt;br /&gt;
local CONTROL_LEFT      = 0x4&lt;br /&gt;
local CONTROL_RIGHT     = 0x8&lt;br /&gt;
local PRIM_ROT_LOCAL    = integer(29)&lt;br /&gt;
&lt;br /&gt;
-- These two will hold the link numbers for left and right front wheels&lt;br /&gt;
local LINK_WHEEL_LEFT = 0&lt;br /&gt;
local LINK_WHEEL_RIGHT = 0&lt;br /&gt;
&lt;br /&gt;
-- Integra Type R Engine properties&lt;br /&gt;
local Engine = {&lt;br /&gt;
    rpm = 800,              -- current engine RPM (starts at idle)&lt;br /&gt;
    gear = 0,               -- 0 = neutral; -1 = reverse; 1-5 for forward gears&lt;br /&gt;
    idleRPM = 800,          -- idle RPM&lt;br /&gt;
    maxRPM = 8300,          -- redline&lt;br /&gt;
    throttle = 0.0,         -- throttle value (0.0 to 1.0)&lt;br /&gt;
    gearRatios = {          -- Integra Type R gear ratios&lt;br /&gt;
        [-1] = 3.727,       -- reverse gear ratio&lt;br /&gt;
        [0]  = 0,          -- neutral&lt;br /&gt;
        [1]  = 3.727,&lt;br /&gt;
        [2]  = 2.256,&lt;br /&gt;
        [3]  = 1.729,&lt;br /&gt;
        [4]  = 1.416,&lt;br /&gt;
        [5]  = 1.194&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FINAL_DRIVE = 4.1   -- Final drive ratio&lt;br /&gt;
local WHEEL_RADIUS = 0.3  -- in meters&lt;br /&gt;
&lt;br /&gt;
-- Vehicle properties (using realistic Integra Type R mass)&lt;br /&gt;
local Vehicle = {&lt;br /&gt;
    speed = 0,      -- speed in m/s; negative indicates reverse motion&lt;br /&gt;
    mass = 1200,    -- mass in kg&lt;br /&gt;
    angle = 0,      -- heading angle in radians&lt;br /&gt;
    steering = 0,   -- steering input: -1 (left), 0 (none), 1 (right)&lt;br /&gt;
    brake = false   -- flag indicating if brakes are applied&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Engine constants (using realistic Integra Type R values)&lt;br /&gt;
local MAX_TORQUE = 200   -- in Nm&lt;br /&gt;
&lt;br /&gt;
-- Brake force constant (for active braking)&lt;br /&gt;
local BRAKE_FORCE = 4000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Define a torque curve function:&lt;br /&gt;
local function torqueCurve(rpm)&lt;br /&gt;
    local peakRPM = 6000&lt;br /&gt;
    if rpm &amp;lt; peakRPM then&lt;br /&gt;
        return 0.8 + 0.2 * ((rpm - Engine.idleRPM) / (peakRPM - Engine.idleRPM))&lt;br /&gt;
    else&lt;br /&gt;
        local factor = 1 - 0.5 * ((rpm - peakRPM) / (Engine.maxRPM - peakRPM))&lt;br /&gt;
        return math.max(0.5, factor)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Engine braking force when throttle is zero in gear.&lt;br /&gt;
local ENGINE_BRAKE_FORCE = 2000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Friction and drag constants&lt;br /&gt;
local G = 9.81            -- gravity (m/s^2)&lt;br /&gt;
local C_rr_inGear = 0.015 -- rolling resistance coefficient in gear&lt;br /&gt;
local C_rr_neutral = 0.05 -- rolling resistance coefficient in neutral&lt;br /&gt;
&lt;br /&gt;
local airDensity = 1.225      -- kg/m^3&lt;br /&gt;
local dragCoefficient = 0.3   -- aerodynamic drag coefficient&lt;br /&gt;
local frontalArea = 2.2       -- in m^2&lt;br /&gt;
&lt;br /&gt;
local function getAerodynamicDrag(speed)&lt;br /&gt;
    return 0.5 * airDensity * dragCoefficient * frontalArea * speed * speed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Compute engine RPM from vehicle speed when in gear.&lt;br /&gt;
local function computeEngineRPMFromSpeed()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return Engine.rpm  -- In neutral, RPM is managed independently.&lt;br /&gt;
    else&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local rpm = math.abs(Vehicle.speed) * conversionFactor&lt;br /&gt;
        if rpm &amp;lt; Engine.idleRPM then rpm = Engine.idleRPM end&lt;br /&gt;
        return rpm&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- For neutral, update RPM toward a target based on throttle.&lt;br /&gt;
local function updateEngineRPMNeutral()&lt;br /&gt;
    local targetRPM = Engine.idleRPM + (Engine.maxRPM - Engine.idleRPM) * Engine.throttle&lt;br /&gt;
    local changeRate = 200  -- RPM per tick&lt;br /&gt;
    if Engine.rpm &amp;lt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.min(Engine.rpm + changeRate, targetRPM)&lt;br /&gt;
    elseif Engine.rpm &amp;gt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.max(Engine.rpm - changeRate, targetRPM)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Calculate the engine force delivered to the wheels.&lt;br /&gt;
local function getEngineForce()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return 0  -- In neutral, no drive force is transmitted.&lt;br /&gt;
    else&lt;br /&gt;
        if Engine.throttle == 0 then&lt;br /&gt;
            -- Apply engine braking when throttle is released.&lt;br /&gt;
            return -ENGINE_BRAKE_FORCE&lt;br /&gt;
        else&lt;br /&gt;
            local torque = Engine.throttle * MAX_TORQUE * torqueCurve(Engine.rpm)&lt;br /&gt;
            local force = (torque * Engine.gearRatios[Engine.gear] * FINAL_DRIVE) / WHEEL_RADIUS&lt;br /&gt;
            return force&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Starts the engine by setting it to idle RPM.&lt;br /&gt;
local function startEngine()&lt;br /&gt;
    Engine.rpm = Engine.idleRPM&lt;br /&gt;
    ll.Say(0, &amp;quot;Engine started. RPM: &amp;quot; .. Engine.rpm)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Shifts the gear if valid.&lt;br /&gt;
local function shiftGear(newGear)&lt;br /&gt;
    if Engine.gearRatios[newGear] then&lt;br /&gt;
        Engine.gear = newGear&lt;br /&gt;
        ll.Say(0, &amp;quot;Shifted to gear &amp;quot; .. newGear)&lt;br /&gt;
    else&lt;br /&gt;
        ll.Say(0, &amp;quot;Invalid gear: &amp;quot; .. newGear)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function findWheels()&lt;br /&gt;
    local total = ll.GetNumberOfPrims()&lt;br /&gt;
    for i = 1, total do&lt;br /&gt;
        local name = ll.GetLinkName(i)&lt;br /&gt;
        if name == &amp;quot;WHEEL_LF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_LEFT = i&lt;br /&gt;
        elseif name == &amp;quot;WHEEL_RF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_RIGHT = i&lt;br /&gt;
        end&lt;br /&gt;
    end &lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local castRayCounter = 0&lt;br /&gt;
local lastGrounded = false&lt;br /&gt;
&lt;br /&gt;
local function isVehicleOnGround()&lt;br /&gt;
    castRayCounter = castRayCounter + 1&lt;br /&gt;
    if castRayCounter &amp;gt;= 5 then&lt;br /&gt;
        castRayCounter = 0&lt;br /&gt;
        local pos = ll.GetPos()&lt;br /&gt;
        local rayDistance = 4.5  -- threshold distance (in meters) to consider as &amp;quot;grounded&amp;quot;&lt;br /&gt;
        local rayStart = pos&lt;br /&gt;
        local rayEnd = pos + vector(0, 0, -rayDistance)&lt;br /&gt;
        local hitResult = ll.CastRay(rayStart, rayEnd, {})&lt;br /&gt;
        if table.getn(hitResult) == 3 then&lt;br /&gt;
            lastGrounded = true&lt;br /&gt;
        else&lt;br /&gt;
            lastGrounded = false&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return lastGrounded&lt;br /&gt;
end &lt;br /&gt;
&lt;br /&gt;
-- Timer event: updates engine, vehicle physics, steering, and rotates the wheels based on steering.&lt;br /&gt;
function timer()&lt;br /&gt;
    local dt = 0.1  -- time step in seconds&lt;br /&gt;
&lt;br /&gt;
    -- For neutral, update RPM based on throttle.&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        updateEngineRPMNeutral()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Calculate forces.&lt;br /&gt;
    local C_rr = (Engine.gear == 0) and C_rr_neutral or C_rr_inGear&lt;br /&gt;
    local F_roll = Vehicle.mass * G * C_rr&lt;br /&gt;
    local engineForce = getEngineForce()&lt;br /&gt;
    local dragForce = getAerodynamicDrag(math.abs(Vehicle.speed))&lt;br /&gt;
    local netForce = engineForce - (F_roll + dragForce)&lt;br /&gt;
&lt;br /&gt;
    -- Apply braking force if brakes are engaged.&lt;br /&gt;
    if Vehicle.brake then&lt;br /&gt;
        netForce = netForce - BRAKE_FORCE&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local acceleration = netForce / Vehicle.mass&lt;br /&gt;
    local vel = ll.GetVel()&lt;br /&gt;
    local horizontal_vec = vector(vel.x, vel.y, 0) &lt;br /&gt;
    Vehicle.speed = (ll.VecMag(horizontal_vec) + acceleration * dt)&lt;br /&gt;
&lt;br /&gt;
    -- Limit vehicle speed based on gear.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        if Vehicle.speed &amp;lt; 0 then &lt;br /&gt;
            Vehicle.speed = 0 &lt;br /&gt;
        end&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local maxSpeed = Engine.maxRPM / conversionFactor&lt;br /&gt;
        if Vehicle.speed &amp;gt; maxSpeed then&lt;br /&gt;
            Vehicle.speed = maxSpeed&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- When in gear, update RPM based on the new speed.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        Engine.rpm = computeEngineRPMFromSpeed()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Steering update using angular velocity and quaternions:&lt;br /&gt;
    local STEERING_RATE = math.rad(6)  -- desired steering rate per tick (radians)&lt;br /&gt;
    local yawDelta = Vehicle.steering * STEERING_RATE  -- small rotation for this tick&lt;br /&gt;
&lt;br /&gt;
    if yawDelta ~= 0 then&lt;br /&gt;
        local deltaQuat = ll.Euler2Rot(vector(0, 0, yawDelta))&lt;br /&gt;
        local angularSpeed = yawDelta / dt&lt;br /&gt;
        local angularVelocity = vector(0, 0, angularSpeed)&lt;br /&gt;
        ll.SetAngularVelocity(angularVelocity, 1)&lt;br /&gt;
        Vehicle.angle = Vehicle.angle + yawDelta&lt;br /&gt;
    else&lt;br /&gt;
        ll.SetAngularVelocity(vector(0, 0, 0), 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Display engine RPM and speed (converted to mph).&lt;br /&gt;
    local speed_mph = math.abs(Vehicle.speed) * 2.23694&lt;br /&gt;
    ll.SetText(&amp;quot;RPM: &amp;quot; .. math.floor(Engine.rpm) ..&lt;br /&gt;
               &amp;quot; | Speed: &amp;quot; .. string.format(&amp;quot;%.1f&amp;quot;, speed_mph) ..&lt;br /&gt;
               &amp;quot; mph&amp;quot;, vector(0,0,0), 1)&lt;br /&gt;
&lt;br /&gt;
    -- Only update velocity if the vehicle is near the ground, and we&#039;re in gear.&lt;br /&gt;
    if isVehicleOnGround() and Engine.gear ~= 0 then&lt;br /&gt;
        local velocityVector&lt;br /&gt;
        if Engine.gear &amp;gt; 0 then&lt;br /&gt;
            velocityVector = vector(Vehicle.speed, 0, 0)&lt;br /&gt;
        else&lt;br /&gt;
            -- Reverse gear&lt;br /&gt;
            velocityVector = vector(-Vehicle.speed, 0, 0)&lt;br /&gt;
        end&lt;br /&gt;
        ll.SetVelocity(velocityVector, 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Update the wheel rotations to visually match the steering.&lt;br /&gt;
    local maxWheelTurn = math.rad(30)  -- maximum wheel turn angle in radians&lt;br /&gt;
    local wheelAngle = -Vehicle.steering * maxWheelTurn&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_LEFT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_LEFT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(-4.71239, -wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_RIGHT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_RIGHT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(4.71239, wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched: starts engine and simulation.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    ll.Say(0, &amp;quot;Touch detected, requesting controls and starting engine.&amp;quot;)&lt;br /&gt;
    ll.RequestPermissions(ll.GetOwner(), PERMISSION_TAKE_CONTROLS)&lt;br /&gt;
    startEngine()&lt;br /&gt;
    findWheels()&lt;br /&gt;
    ll.SetTimerEvent(0.1)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when runtime permissions are granted.&lt;br /&gt;
function run_time_permissions(perm)&lt;br /&gt;
    if bit32.band(perm, PERMISSION_TAKE_CONTROLS) then&lt;br /&gt;
        ll.Say(0, &amp;quot;Permissions granted.&amp;quot;)&lt;br /&gt;
        local controlMask = bit32.bor(CONTROL_FWD, CONTROL_BACK, CONTROL_UP, CONTROL_DOWN, CONTROL_LEFT, CONTROL_RIGHT)&lt;br /&gt;
        ll.TakeControls(controlMask, 1, 1)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Handle control events:&lt;br /&gt;
-- • CONTROL_FWD applies throttle.&lt;br /&gt;
-- • CONTROL_BACK applies the brakes.&lt;br /&gt;
-- • CONTROL_UP and CONTROL_DOWN shift gears.&lt;br /&gt;
-- • CONTROL_LEFT and CONTROL_RIGHT steer the vehicle.&lt;br /&gt;
function control(avatar_id, level, edge)&lt;br /&gt;
    local start = bit32.band(level, edge)            &lt;br /&gt;
    local finish = bit32.band(bit32.bnot(level), edge) &lt;br /&gt;
    local held = bit32.band(level, bit32.bnot(edge))   &lt;br /&gt;
    local untouched = bit32.bnot(bit32.bor(level, edge)) &lt;br /&gt;
&lt;br /&gt;
    if bit32.band(held, CONTROL_BACK) ~= 0 then&lt;br /&gt;
        Vehicle.brake = true&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    elseif bit32.band(held, CONTROL_FWD) ~= 0 then&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 1.0&lt;br /&gt;
        if Engine.gear == 0 then&lt;br /&gt;
            updateEngineRPMNeutral()&lt;br /&gt;
        end&lt;br /&gt;
    else&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_UP) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear + 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_DOWN) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear - 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local steeringInput = 0&lt;br /&gt;
    if bit32.band(held, CONTROL_LEFT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput + 1&lt;br /&gt;
    end&lt;br /&gt;
    if bit32.band(held, CONTROL_RIGHT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput - 1&lt;br /&gt;
    end&lt;br /&gt;
    Vehicle.steering = steeringInput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== weather_box.lua ===&lt;br /&gt;
This script demonstrates coroutines, as well as working with a JSON-based web API:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local TEXTBOX_CHANNEL = 323242&lt;br /&gt;
-- kludge because we don&#039;t have LSL constants in here yet&lt;br /&gt;
local HTTP_BODY_MAXLENGTH = integer(2)&lt;br /&gt;
local PRIM_TEXT = integer(26)&lt;br /&gt;
local PRIM_SIZE = integer(7)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
gCityName = &amp;quot;&amp;quot;&lt;br /&gt;
gLatLong = nil  -- A table of 2 elems when it&#039;s populated&lt;br /&gt;
gCityRequestTask = nil&lt;br /&gt;
gWeatherRequestTask = nil&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- JSON stuff&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- From https://github.com/rxi/json.lua/blob/master/json.lua&lt;br /&gt;
&lt;br /&gt;
local json = {}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map = {&lt;br /&gt;
  [ &amp;quot;\\&amp;quot; ] = &amp;quot;\\&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\&amp;quot;&amp;quot; ] = &amp;quot;\&amp;quot;&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\b&amp;quot; ] = &amp;quot;b&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\f&amp;quot; ] = &amp;quot;f&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\n&amp;quot; ] = &amp;quot;n&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\r&amp;quot; ] = &amp;quot;r&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\t&amp;quot; ] = &amp;quot;t&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map_inv = { [ &amp;quot;/&amp;quot; ] = &amp;quot;/&amp;quot; }&lt;br /&gt;
for k, v in pairs(escape_char_map) do&lt;br /&gt;
  escape_char_map_inv[v] = k&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local parse&lt;br /&gt;
&lt;br /&gt;
local function create_set(...)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  for i = 1, select(&amp;quot;#&amp;quot;, ...) do&lt;br /&gt;
    res[ select(i, ...) ] = true&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local space_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;)&lt;br /&gt;
local delim_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, &amp;quot;]&amp;quot;, &amp;quot;}&amp;quot;, &amp;quot;,&amp;quot;)&lt;br /&gt;
local escape_chars  = create_set(&amp;quot;\\&amp;quot;, &amp;quot;/&amp;quot;, &#039;&amp;quot;&#039;, &amp;quot;b&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;r&amp;quot;, &amp;quot;t&amp;quot;, &amp;quot;u&amp;quot;)&lt;br /&gt;
local literals      = create_set(&amp;quot;true&amp;quot;, &amp;quot;false&amp;quot;, &amp;quot;null&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local literal_map = {&lt;br /&gt;
  [ &amp;quot;true&amp;quot;  ] = true,&lt;br /&gt;
  [ &amp;quot;false&amp;quot; ] = false,&lt;br /&gt;
  [ &amp;quot;null&amp;quot;  ] = nil,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function next_char(str, idx, set, negate)&lt;br /&gt;
  for i = idx, #str do&lt;br /&gt;
    if set[str:sub(i, i)] ~= negate then&lt;br /&gt;
      return i&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  return #str + 1&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function decode_error(str, idx, msg)&lt;br /&gt;
  local line_count = 1&lt;br /&gt;
  local col_count = 1&lt;br /&gt;
  for i = 1, idx - 1 do&lt;br /&gt;
    col_count = col_count + 1&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;\n&amp;quot; then&lt;br /&gt;
      line_count = line_count + 1&lt;br /&gt;
      col_count = 1&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;%s at line %d col %d&amp;quot;, msg, line_count, col_count) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function codepoint_to_utf8(n)&lt;br /&gt;
  -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&amp;amp;id=iws-appendixa&lt;br /&gt;
  local f = math.floor&lt;br /&gt;
  if n &amp;lt;= 0x7f then&lt;br /&gt;
    return string.char(n)&lt;br /&gt;
  elseif n &amp;lt;= 0x7ff then&lt;br /&gt;
    return string.char(f(n / 64) + 192, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0xffff then&lt;br /&gt;
    return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0x10ffff then&lt;br /&gt;
    return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,&lt;br /&gt;
                       f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;invalid unicode codepoint &#039;%x&#039;&amp;quot;, n) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_unicode_escape(s)&lt;br /&gt;
  local n1 = tonumber( s:sub(1, 4),  16 )&lt;br /&gt;
  local n2 = tonumber( s:sub(7, 10), 16 )&lt;br /&gt;
   -- Surrogate pair?&lt;br /&gt;
  if n2 then&lt;br /&gt;
    return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)&lt;br /&gt;
  else&lt;br /&gt;
    return codepoint_to_utf8(n1)&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_string(str, i)&lt;br /&gt;
  local res = &amp;quot;&amp;quot;&lt;br /&gt;
  local j = i + 1&lt;br /&gt;
  local k = j&lt;br /&gt;
&lt;br /&gt;
  while j &amp;lt;= #str do&lt;br /&gt;
    local x = str:byte(j)&lt;br /&gt;
&lt;br /&gt;
    if x &amp;lt; 32 then&lt;br /&gt;
      decode_error(str, j, &amp;quot;control character in string&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    elseif x == 92 then -- `\`: Escape&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      j = j + 1&lt;br /&gt;
      local c = str:sub(j, j)&lt;br /&gt;
      if c == &amp;quot;u&amp;quot; then&lt;br /&gt;
        local hex = str:match(&amp;quot;^[dD][89aAbB]%x%x\\u%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or str:match(&amp;quot;^%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or decode_error(str, j - 1, &amp;quot;invalid unicode escape in string&amp;quot;)&lt;br /&gt;
        res = res .. parse_unicode_escape(hex)&lt;br /&gt;
        j = j + #hex&lt;br /&gt;
      else&lt;br /&gt;
        if not escape_chars[c] then&lt;br /&gt;
          decode_error(str, j - 1, &amp;quot;invalid escape char &#039;&amp;quot; .. c .. &amp;quot;&#039; in string&amp;quot;)&lt;br /&gt;
        end&lt;br /&gt;
        res = res .. escape_char_map_inv[c]&lt;br /&gt;
      end&lt;br /&gt;
      k = j + 1&lt;br /&gt;
&lt;br /&gt;
    elseif x == 34 then -- `&amp;quot;`: End of string&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      return res, j + 1&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    j = j + 1&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  decode_error(str, i, &amp;quot;expected closing quote for string&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_number(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local s = str:sub(i, x - 1)&lt;br /&gt;
  local n = tonumber(s)&lt;br /&gt;
  if not n then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid number &#039;&amp;quot; .. s .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return n, x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_literal(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local word = str:sub(i, x - 1)&lt;br /&gt;
  if not literals[word] then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid literal &#039;&amp;quot; .. word .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return literal_map[word], x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_array(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  local n = 1&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local x&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of array?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;]&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read token&lt;br /&gt;
    x, i = parse(str, i)&lt;br /&gt;
    res[n] = x&lt;br /&gt;
    n = n + 1&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;]&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;]&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_object(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local key, val&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of object?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;}&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read key&lt;br /&gt;
    if str:sub(i, i) ~= &#039;&amp;quot;&#039; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected string for key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    key, i = parse(str, i)&lt;br /&gt;
    -- Read &#039;:&#039; delimiter&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    if str:sub(i, i) ~= &amp;quot;:&amp;quot; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected &#039;:&#039; after key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    i = next_char(str, i + 1, space_chars, true)&lt;br /&gt;
    -- Read value&lt;br /&gt;
    val, i = parse(str, i)&lt;br /&gt;
    -- Set&lt;br /&gt;
    res[key] = val&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;}&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;}&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local char_func_map = {&lt;br /&gt;
  [ &#039;&amp;quot;&#039; ] = parse_string,&lt;br /&gt;
  [ &amp;quot;0&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;1&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;2&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;3&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;4&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;5&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;6&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;7&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;8&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;9&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;-&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;t&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;f&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;n&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;[&amp;quot; ] = parse_array,&lt;br /&gt;
  [ &amp;quot;{&amp;quot; ] = parse_object,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
parse = function(str, idx)&lt;br /&gt;
  local chr = str:sub(idx, idx)&lt;br /&gt;
  local f = char_func_map[chr]&lt;br /&gt;
  if f then&lt;br /&gt;
    return f(str, idx)&lt;br /&gt;
  end&lt;br /&gt;
  decode_error(str, idx, &amp;quot;unexpected character &#039;&amp;quot; .. chr .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function json.decode(str)&lt;br /&gt;
  if type(str) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
    error(&amp;quot;expected argument of type string, got &amp;quot; .. type(str))&lt;br /&gt;
  end&lt;br /&gt;
  local res, idx = parse(str, next_char(str, 1, space_chars, true))&lt;br /&gt;
  idx = next_char(str, idx, space_chars, true)&lt;br /&gt;
  if idx &amp;lt;= #str then&lt;br /&gt;
    decode_error(str, idx, &amp;quot;trailing garbage&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Loop&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- A naive little event loop that lets you track running coroutines and dispatches&lt;br /&gt;
-- the events they&#039;re waiting for to them.&lt;br /&gt;
&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; awaited event&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    -- false has no semantic meaning here, we just want to reserve the space for&lt;br /&gt;
    -- the coroutine in the table without saying we&#039;re awaiting anything yet.&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- (internal) run the event&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    assert(coroutine.status(coro) ~= &amp;quot;dead&amp;quot;)&lt;br /&gt;
    -- assert(not self.running)&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    -- Since we&#039;re running, this isn&#039;t currently awaiting anything&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local success, ret = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    -- If we&#039;re not done then ret should be the kind of event we&#039;re awaiting.&lt;br /&gt;
    -- Otherwise it&#039;s an error message or something. Who cares.&lt;br /&gt;
    if not success then&lt;br /&gt;
        -- Get rid of the coro&lt;br /&gt;
        if ret then&lt;br /&gt;
            ll.OwnerSay(`Might have an error: {ret}`)&lt;br /&gt;
        end&lt;br /&gt;
        ret = nil&lt;br /&gt;
    end&lt;br /&gt;
    self._coros[coro] = ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Wake up any tasks that are waiting for this kind of event&lt;br /&gt;
function EventLoop:handle_event(name, ...)&lt;br /&gt;
    -- We may mutate self._coros, so do a clone&lt;br /&gt;
    local coros = table.clone(self._coros)&lt;br /&gt;
    ll.OwnerSay(`Handling {name}`)&lt;br /&gt;
&lt;br /&gt;
    for coro, awaited_event in coros do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            -- Hmm, this coroutine is dead, prune it.&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if awaited_event == name then&lt;br /&gt;
            ll.OwnerSay(`Dispatching {name} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_event_catcher(name)&lt;br /&gt;
    local function catcher(...)&lt;br /&gt;
        ll.OwnerSay(&amp;quot;yay&amp;quot;)&lt;br /&gt;
        EventLoop:handle_event(name, ...)&lt;br /&gt;
    end&lt;br /&gt;
    return catcher&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Helper function for waiting and telling the EventLoop what event you want&lt;br /&gt;
local function await_event(kind)&lt;br /&gt;
    -- You had better do this inside a running event loop&lt;br /&gt;
    assert(EventLoop.running)&lt;br /&gt;
    -- This will yield, the EventLoop will resume us once it has&lt;br /&gt;
    -- an event we might be interested in.&lt;br /&gt;
    return coroutine.yield(kind)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Script-specific functions&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
local function hide_children()&lt;br /&gt;
    for i=2,ll.GetNumberOfPrims() do&lt;br /&gt;
        -- Hide all the child prims&lt;br /&gt;
        ll.SetLinkAlpha(i, 0, -1)&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(i, {&lt;br /&gt;
            PRIM_SIZE, vector(0.5, 0.5, 0.5),&lt;br /&gt;
            PRIM_TEXT, &amp;quot;&amp;quot;, vector(1,1,1), 1&lt;br /&gt;
        })&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_textbox(avatar_id, message, channel)&lt;br /&gt;
    ll.TextBox(avatar_id, message, channel)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        local resp_channel, name, id, resp = await_event(&amp;quot;listen&amp;quot;)&lt;br /&gt;
        if id ~= avatar_id or channel ~= resp_channel then&lt;br /&gt;
            -- If this isn&#039;t a message we&#039;re interested in then wait for the next event&lt;br /&gt;
            ll.OwnerSay(`{id}, {avatar_id}, {resp_channel}, {channel}`)&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return resp&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function make_json_request(url)&lt;br /&gt;
    local req_id = ll.HTTPRequest(url, {HTTP_BODY_MAXLENGTH, integer(16000)}, &amp;quot;&amp;quot;)&lt;br /&gt;
    while true do&lt;br /&gt;
        local ev_id, status, metadata, body = await_event(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
        if ev_id ~= req_id then&lt;br /&gt;
            -- Not a response for the HTTP request we were waiting on, wait for the next event.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return status, metadata, body&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function request_weather()&lt;br /&gt;
    -- And you can use a wrapper like this to make an async function callable within both normal and coroutine contexts.&lt;br /&gt;
    -- Normally this sort of thing would not be necessary, but here it is since the EventLoop lives in our own code,&lt;br /&gt;
    -- and isn&#039;t a property of the actual script engine.&lt;br /&gt;
    if gWeatherRequestTask then&lt;br /&gt;
        EventLoop:kill_task(gWeatherRequestTask)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    gWeatherRequestTask = EventLoop:create_task(function()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Requesting weather&amp;quot;)&lt;br /&gt;
        local status, metadata, body = make_json_request(`https://api.open-meteo.com/v1/forecast?latitude={gLatLong[1]}&amp;amp;longitude={gLatLong[2]}&amp;amp;current=temperature_2m&amp;amp;daily=temperature_2m_max`)&lt;br /&gt;
        if status ~= 200 then&lt;br /&gt;
            ll.OwnerSay(&amp;quot;Error requesting weather&amp;quot;)&lt;br /&gt;
            return&lt;br /&gt;
        end&lt;br /&gt;
        local parsed = json.decode(body)&lt;br /&gt;
&lt;br /&gt;
        local current = parsed[&amp;quot;current&amp;quot;]&lt;br /&gt;
        ll.OwnerSay(`Temperature is {current[&amp;quot;temperature_2m&amp;quot;]}C`)&lt;br /&gt;
        ll.SetText(`Temperature in {gCityName} is currently {current[&amp;quot;temperature_2m&amp;quot;]}C`, vector(1,1,1), 1)&lt;br /&gt;
&lt;br /&gt;
        -- Show the highs for each day&lt;br /&gt;
        local days = parsed[&amp;quot;daily&amp;quot;][&amp;quot;temperature_2m_max&amp;quot;]&lt;br /&gt;
        for i=1,#days do&lt;br /&gt;
            local link_num = i + 1&lt;br /&gt;
            -- Higher temps = bigger on Z, negatives are unrepresentable :)&lt;br /&gt;
            local z_size = (days[i] / 30) * 2&lt;br /&gt;
            ll.SetLinkAlpha(link_num, 1, -1)&lt;br /&gt;
            ll.SetLinkPrimitiveParamsFast(link_num, {&lt;br /&gt;
                PRIM_SIZE, vector(0.5, 0.5, z_size),&lt;br /&gt;
                PRIM_TEXT, `{days[i]}C`, vector(1,1,1), 1&lt;br /&gt;
            })&lt;br /&gt;
        end&lt;br /&gt;
    end)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ask_for_city()&lt;br /&gt;
    local resp = send_textbox(ll.GetOwner(), &amp;quot;What city do you want the weather for?&amp;quot;, TEXTBOX_CHANNEL)&lt;br /&gt;
    if not resp then&lt;br /&gt;
        ll.OwnerSay(&amp;quot;Got no response for city request.&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    ll.OwnerSay(`You want the weather for {resp}.`)&lt;br /&gt;
    gLatLong = nil&lt;br /&gt;
    gCityName = resp&lt;br /&gt;
&lt;br /&gt;
    hide_children()&lt;br /&gt;
&lt;br /&gt;
    -- request the lat and long for this city&lt;br /&gt;
    local status, metadata, body = make_json_request(`https://nominatim.openstreetmap.org/search.php?city={ll.EscapeURL(resp)}&amp;amp;format=jsonv2`)&lt;br /&gt;
    local parsed = json.decode(body)&lt;br /&gt;
    if not #parsed then&lt;br /&gt;
        ll.OwnerSay(`Got no results for {resp}!`)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local first_result = parsed[1]&lt;br /&gt;
    gLatLong = {first_result[&amp;quot;lat&amp;quot;], first_result[&amp;quot;lon&amp;quot;]}&lt;br /&gt;
    ll.OwnerSay(`City Lat: {gLatLong[1]}, Long: {gLatLong[2]}`)&lt;br /&gt;
&lt;br /&gt;
    request_weather()&lt;br /&gt;
&lt;br /&gt;
    -- request weather again in 3 mins&lt;br /&gt;
    ll.SetTimerEvent(180)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Handlers&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- If we don&#039;t care about conventionally dispatched events, we can just set the event handler to directly send&lt;br /&gt;
-- event data to the event loop.&lt;br /&gt;
http_response = EventLoop:create_event_catcher(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
listen = EventLoop:create_event_catcher(&amp;quot;listen&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- More conventional event handlers work too.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    for i=0,num-1 do&lt;br /&gt;
        if ll.GetOwner() ~= ll.DetectedKey(i) then&lt;br /&gt;
            -- Go to next loop if it wasn&#039;t the owner touching us.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if gCityRequestTask then&lt;br /&gt;
            EventLoop:kill_task(gCityRequestTask)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Spin up a task to ask the user for their city&lt;br /&gt;
        gCityRequestTask = EventLoop:create_task(ask_for_city)&lt;br /&gt;
        break&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function timer()&lt;br /&gt;
    request_weather()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Not strictly necessary, but I like having the main function have its own scope.&lt;br /&gt;
-- Rather than just have top-level logic.&lt;br /&gt;
local function main()&lt;br /&gt;
    ll.Listen(TEXTBOX_CHANNEL, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.SetText(&amp;quot;&amp;quot;, vector(1,1,1), 1)&lt;br /&gt;
    hide_children()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Weatherbox operational!&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
main()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218007</id>
		<title>Lua Alpha</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218007"/>
		<updated>2025-03-04T22:43:28Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:Luau.png|720px|thumb|right|Luau logo]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{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.}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Second Life Luau Alpha =&lt;br /&gt;
&lt;br /&gt;
We&#039;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.&lt;br /&gt;
&lt;br /&gt;
== What is Luau? ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Why Luau? ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== How to Get Started with Luau in Second Life ==&lt;br /&gt;
&lt;br /&gt;
In order to play with Luau, you&#039;ll need to download our Lua project viewer, and log onto our Aditi beta grid.&lt;br /&gt;
&lt;br /&gt;
* Access the latest build of the Luau-enabled Second Life Viewer from [https://releasenotes.secondlife.com/viewer/7.1.12.13526902562.html here].&lt;br /&gt;
&lt;br /&gt;
Once you&#039;ve got the new viewer and have logged onto the beta grid, head over to these Luau-enabled regions:&lt;br /&gt;
* Luau Yardang&lt;br /&gt;
* Luau Tombolo&lt;br /&gt;
* Luau Mesa&lt;br /&gt;
* Luau Tideland&lt;br /&gt;
&lt;br /&gt;
When editing a script in the new Lua project viewer, you&#039;ll notice a new &#039;&#039;&#039;Compiler&#039;&#039;&#039; 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).&lt;br /&gt;
&lt;br /&gt;
[[File:Compiler_dropdown.png|Compiler selection dropdown]]&lt;br /&gt;
&lt;br /&gt;
Compiler drop-down options:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;LSL: Legacy (LSO2)&#039;&#039;&#039; - Scripts written in LSL, to be run on the old LSO2 VM&lt;br /&gt;
* &#039;&#039;&#039;LSL: Mono&#039;&#039;&#039;- Scripts written in LSL, to be run on the Mono VM&lt;br /&gt;
* &#039;&#039;&#039;Lua&#039;&#039;&#039; - Scripts written in Lua, to be run on the Luau VM&lt;br /&gt;
* &#039;&#039;&#039;LSL/Luau&#039;&#039;&#039;- Scripts written in LSL, to be run on the Luau VM&lt;br /&gt;
&lt;br /&gt;
=== Transitioning from LSL to Luau ===&lt;br /&gt;
* &#039;&#039;&#039;Function Namespacing:&#039;&#039;&#039;&lt;br /&gt;
** In Luau, Linden Lab functions have been moved under the &#039;&#039;&#039;ll&#039;&#039;&#039; namespace.&lt;br /&gt;
** For example:&lt;br /&gt;
*** &#039;&#039;llSay&#039;&#039; becomes &#039;&#039;ll.Say&#039;&#039;&lt;br /&gt;
*** &#039;&#039;llGetPos&#039;&#039; becomes &#039;&#039;ll.GetPos&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Constants:&#039;&#039;&#039;&lt;br /&gt;
** Constants like &#039;&#039;PERMISSION_TAKE_CONTROLS&#039;&#039; and &#039;&#039;PRIM_ROT_LOCAL&#039;&#039; are not predefined in Luau.&lt;br /&gt;
** You must specify these constants if you wish to reference them in your scripts.&lt;br /&gt;
&lt;br /&gt;
=== Luau Libraries ===&lt;br /&gt;
* &#039;&#039;&#039;Coroutines:&#039;&#039;&#039;&lt;br /&gt;
** Luau supports coroutines, allowing for cooperative multitasking within scripts.&lt;br /&gt;
** Key functions include:&lt;br /&gt;
*** &#039;&#039;coroutine.create&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.status&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.resume&#039;&#039;&lt;br /&gt;
** Refer to the [https://luau.org/library#coroutine-library coroutine library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Bitwise Operations:&#039;&#039;&#039;&lt;br /&gt;
** Luau includes a &#039;&#039;bit32&#039;&#039; library for bitwise operations, enabling more efficient data manipulation.&lt;br /&gt;
** Refer to the [https://luau.org/library#bit32-library bit32 library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Standard Library:&#039;&#039;&#039;&lt;br /&gt;
** Luau comes equipped with a standard library of functions designed to manipulate built-in data types.&lt;br /&gt;
** Explore the [https://luau.org/library Luau Standard Library] for a comprehensive list of available functions.&lt;br /&gt;
&lt;br /&gt;
== Feedback and Support ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== Example Scripts ==&lt;br /&gt;
&lt;br /&gt;
To help you get started, we&#039;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&#039;s desire!&lt;br /&gt;
&lt;br /&gt;
=== dialog.lua ===&lt;br /&gt;
This script demonstrates how one can interact with dialog menus.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Define the menu buttons and dialog message.&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local ToucherID = nil&lt;br /&gt;
local dialogChannel = nil&lt;br /&gt;
local listenHandle = nil&lt;br /&gt;
&lt;br /&gt;
-- This function is called when the script first starts.&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Get the object&#039;s key and compute a dialog channel number.&lt;br /&gt;
    local key = ll.GetKey()&lt;br /&gt;
    -- Extract the last 7 characters of the key and convert it from hex.&lt;br /&gt;
    dialogChannel = -1 - tonumber(string.sub(tostring(key), -7, -1), 16)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    ToucherID = ll.DetectedKey(0)&lt;br /&gt;
    -- If there is already a listen handle, then remove it&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
    end&lt;br /&gt;
    listenHandle = ll.Listen(dialogChannel, &amp;quot;&amp;quot;, ToucherID, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
    -- Set a 60-second timer for response.&lt;br /&gt;
    ll.SetTimerEvent(60.0)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when a dialog response is received.&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    if message == &amp;quot;-&amp;quot; then&lt;br /&gt;
        -- Redisplay the dialog if the &amp;quot;-&amp;quot; option is selected.&lt;br /&gt;
        ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Stop the timer, and stop the listening handler.&lt;br /&gt;
    ll.ListenRemove(listenHandle)&lt;br /&gt;
    ll.SetTimerEvent(0)&lt;br /&gt;
    -- Let the user know what they selected&lt;br /&gt;
    ll.Say(0, `You selected {message}`)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the timer expires.&lt;br /&gt;
function timer()&lt;br /&gt;
    -- Stop the timer and clean up the listener.&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.SetTimerEvent(0)&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
        ll.Whisper(0, &amp;quot;Sorry. You snooze; you lose.&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dialog_coroutine.lua ===&lt;br /&gt;
This script demonstrates how one could use coroutines to handle dialog responses, with multi-user support.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Minimal EventLoop&lt;br /&gt;
-------------------------&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; eventName it’s waiting for&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status(coro) ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Internal helper: resumes a coroutine&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local ok, eventAwaited = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    if not ok then&lt;br /&gt;
        ll.OwnerSay(`Coroutine error: {eventAwaited}`)&lt;br /&gt;
        self._coros[coro] = nil&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- If still alive, &#039;eventAwaited&#039; is the next event it wants&lt;br /&gt;
    self._coros[coro] = eventAwaited&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:handle_event(eventName, ...)&lt;br /&gt;
    ll.OwnerSay(`Handling event {eventName}`)&lt;br /&gt;
    local snapshot = table.clone(self._coros)&lt;br /&gt;
    for coro, waitingFor in pairs(snapshot) do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        elseif waitingFor == eventName then&lt;br /&gt;
            ll.OwnerSay(`Dispatching event {eventName} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Coroutines use this to yield until an event&lt;br /&gt;
local function await_event(name)&lt;br /&gt;
    assert(EventLoop.running, &amp;quot;await_event called outside a coroutine!&amp;quot;)&lt;br /&gt;
    return coroutine.yield(name)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Script Logic&lt;br /&gt;
-------------------------&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- Use the chat listener to feed the event-loop&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    -- We handle all &#039;listen&#039; events via the event-loop&lt;br /&gt;
    EventLoop:handle_event(`listen_{channel}`, channel, name, sender_id, message)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the script starts&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Seed math.random so each new script run doesn’t repeat the same channels&lt;br /&gt;
    math.randomseed(ll.GetUnixTime())&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Script started with random channels for each user.&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- A coroutine function for a single user&#039;s dialog flow&lt;br /&gt;
local function handle_dialog_for_user(userId)&lt;br /&gt;
    -- Use a random channel&lt;br /&gt;
    local channel = math.random(0x1, 0xFFFF)&lt;br /&gt;
    -- Create a listener for that channel&lt;br /&gt;
    local listenHandle = ll.Listen(channel, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        -- Show the user a dialog&lt;br /&gt;
        ll.Dialog(userId, dialogInfo, buttons, channel)&lt;br /&gt;
&lt;br /&gt;
        -- Wait for the next &#039;listen&#039; event (channel, name, sender_id, message)&lt;br /&gt;
        local c, n, sid, msg = await_event(`listen_{channel}`)&lt;br /&gt;
&lt;br /&gt;
        -- If this &amp;quot;listen&amp;quot; event isn&#039;t for our channel/user, ignore it&lt;br /&gt;
        if c == channel and sid == userId then&lt;br /&gt;
            -- If user pressed &amp;quot;-&amp;quot;, re-display the menu, else they picked a final color&lt;br /&gt;
            if msg ~= &amp;quot;-&amp;quot; then&lt;br /&gt;
                ll.Say(0, `{n} selected {msg}`)&lt;br /&gt;
                -- Now that they&#039;ve chosen something else, remove the listener and finish&lt;br /&gt;
                ll.ListenRemove(listenHandle)&lt;br /&gt;
                return&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    for i=0, num_detected-1 do&lt;br /&gt;
        local toucherId = ll.DetectedKey(i)&lt;br /&gt;
        -- Create a separate coroutine for each person who touches&lt;br /&gt;
        EventLoop:create_task(function()&lt;br /&gt;
            handle_dialog_for_user(toucherId)&lt;br /&gt;
        end)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Run state_entry on load:&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== vehicle.lua ===&lt;br /&gt;
This script demonstrates how one may create their own vehicle scripts, which don’t rely on the underlying LL vehicle system. &lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Control and permission constants&lt;br /&gt;
local PERMISSION_TAKE_CONTROLS = 0x4&lt;br /&gt;
local CONTROL_FWD       = 0x1&lt;br /&gt;
local CONTROL_BACK      = 0x2&lt;br /&gt;
local CONTROL_UP        = 0x10&lt;br /&gt;
local CONTROL_DOWN      = 0x20&lt;br /&gt;
local CONTROL_LEFT      = 0x4&lt;br /&gt;
local CONTROL_RIGHT     = 0x8&lt;br /&gt;
local PRIM_ROT_LOCAL    = integer(29)&lt;br /&gt;
&lt;br /&gt;
-- These two will hold the link numbers for left and right front wheels&lt;br /&gt;
local LINK_WHEEL_LEFT = 0&lt;br /&gt;
local LINK_WHEEL_RIGHT = 0&lt;br /&gt;
&lt;br /&gt;
-- Integra Type R Engine properties&lt;br /&gt;
local Engine = {&lt;br /&gt;
    rpm = 800,              -- current engine RPM (starts at idle)&lt;br /&gt;
    gear = 0,               -- 0 = neutral; -1 = reverse; 1-5 for forward gears&lt;br /&gt;
    idleRPM = 800,          -- idle RPM&lt;br /&gt;
    maxRPM = 8300,          -- redline&lt;br /&gt;
    throttle = 0.0,         -- throttle value (0.0 to 1.0)&lt;br /&gt;
    gearRatios = {          -- Integra Type R gear ratios&lt;br /&gt;
        [-1] = 3.727,       -- reverse gear ratio&lt;br /&gt;
        [0]  = 0,          -- neutral&lt;br /&gt;
        [1]  = 3.727,&lt;br /&gt;
        [2]  = 2.256,&lt;br /&gt;
        [3]  = 1.729,&lt;br /&gt;
        [4]  = 1.416,&lt;br /&gt;
        [5]  = 1.194&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FINAL_DRIVE = 4.1   -- Final drive ratio&lt;br /&gt;
local WHEEL_RADIUS = 0.3  -- in meters&lt;br /&gt;
&lt;br /&gt;
-- Vehicle properties (using realistic Integra Type R mass)&lt;br /&gt;
local Vehicle = {&lt;br /&gt;
    speed = 0,      -- speed in m/s; negative indicates reverse motion&lt;br /&gt;
    mass = 1200,    -- mass in kg&lt;br /&gt;
    angle = 0,      -- heading angle in radians&lt;br /&gt;
    steering = 0,   -- steering input: -1 (left), 0 (none), 1 (right)&lt;br /&gt;
    brake = false   -- flag indicating if brakes are applied&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Engine constants (using realistic Integra Type R values)&lt;br /&gt;
local MAX_TORQUE = 200   -- in Nm&lt;br /&gt;
&lt;br /&gt;
-- Brake force constant (for active braking)&lt;br /&gt;
local BRAKE_FORCE = 4000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Define a torque curve function:&lt;br /&gt;
local function torqueCurve(rpm)&lt;br /&gt;
    local peakRPM = 6000&lt;br /&gt;
    if rpm &amp;lt; peakRPM then&lt;br /&gt;
        return 0.8 + 0.2 * ((rpm - Engine.idleRPM) / (peakRPM - Engine.idleRPM))&lt;br /&gt;
    else&lt;br /&gt;
        local factor = 1 - 0.5 * ((rpm - peakRPM) / (Engine.maxRPM - peakRPM))&lt;br /&gt;
        return math.max(0.5, factor)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Engine braking force when throttle is zero in gear.&lt;br /&gt;
local ENGINE_BRAKE_FORCE = 2000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Friction and drag constants&lt;br /&gt;
local G = 9.81            -- gravity (m/s^2)&lt;br /&gt;
local C_rr_inGear = 0.015 -- rolling resistance coefficient in gear&lt;br /&gt;
local C_rr_neutral = 0.05 -- rolling resistance coefficient in neutral&lt;br /&gt;
&lt;br /&gt;
local airDensity = 1.225      -- kg/m^3&lt;br /&gt;
local dragCoefficient = 0.3   -- aerodynamic drag coefficient&lt;br /&gt;
local frontalArea = 2.2       -- in m^2&lt;br /&gt;
&lt;br /&gt;
local function getAerodynamicDrag(speed)&lt;br /&gt;
    return 0.5 * airDensity * dragCoefficient * frontalArea * speed * speed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Compute engine RPM from vehicle speed when in gear.&lt;br /&gt;
local function computeEngineRPMFromSpeed()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return Engine.rpm  -- In neutral, RPM is managed independently.&lt;br /&gt;
    else&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local rpm = math.abs(Vehicle.speed) * conversionFactor&lt;br /&gt;
        if rpm &amp;lt; Engine.idleRPM then rpm = Engine.idleRPM end&lt;br /&gt;
        return rpm&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- For neutral, update RPM toward a target based on throttle.&lt;br /&gt;
local function updateEngineRPMNeutral()&lt;br /&gt;
    local targetRPM = Engine.idleRPM + (Engine.maxRPM - Engine.idleRPM) * Engine.throttle&lt;br /&gt;
    local changeRate = 200  -- RPM per tick&lt;br /&gt;
    if Engine.rpm &amp;lt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.min(Engine.rpm + changeRate, targetRPM)&lt;br /&gt;
    elseif Engine.rpm &amp;gt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.max(Engine.rpm - changeRate, targetRPM)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Calculate the engine force delivered to the wheels.&lt;br /&gt;
local function getEngineForce()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return 0  -- In neutral, no drive force is transmitted.&lt;br /&gt;
    else&lt;br /&gt;
        if Engine.throttle == 0 then&lt;br /&gt;
            -- Apply engine braking when throttle is released.&lt;br /&gt;
            return -ENGINE_BRAKE_FORCE&lt;br /&gt;
        else&lt;br /&gt;
            local torque = Engine.throttle * MAX_TORQUE * torqueCurve(Engine.rpm)&lt;br /&gt;
            local force = (torque * Engine.gearRatios[Engine.gear] * FINAL_DRIVE) / WHEEL_RADIUS&lt;br /&gt;
            return force&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Starts the engine by setting it to idle RPM.&lt;br /&gt;
local function startEngine()&lt;br /&gt;
    Engine.rpm = Engine.idleRPM&lt;br /&gt;
    ll.Say(0, &amp;quot;Engine started. RPM: &amp;quot; .. Engine.rpm)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Shifts the gear if valid.&lt;br /&gt;
local function shiftGear(newGear)&lt;br /&gt;
    if Engine.gearRatios[newGear] then&lt;br /&gt;
        Engine.gear = newGear&lt;br /&gt;
        ll.Say(0, &amp;quot;Shifted to gear &amp;quot; .. newGear)&lt;br /&gt;
    else&lt;br /&gt;
        ll.Say(0, &amp;quot;Invalid gear: &amp;quot; .. newGear)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function findWheels()&lt;br /&gt;
    local total = ll.GetNumberOfPrims()&lt;br /&gt;
    for i = 1, total do&lt;br /&gt;
        local name = ll.GetLinkName(i)&lt;br /&gt;
        if name == &amp;quot;WHEEL_LF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_LEFT = i&lt;br /&gt;
        elseif name == &amp;quot;WHEEL_RF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_RIGHT = i&lt;br /&gt;
        end&lt;br /&gt;
    end &lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local castRayCounter = 0&lt;br /&gt;
local lastGrounded = false&lt;br /&gt;
&lt;br /&gt;
local function isVehicleOnGround()&lt;br /&gt;
    castRayCounter = castRayCounter + 1&lt;br /&gt;
    if castRayCounter &amp;gt;= 5 then&lt;br /&gt;
        castRayCounter = 0&lt;br /&gt;
        local pos = ll.GetPos()&lt;br /&gt;
        local rayDistance = 4.5  -- threshold distance (in meters) to consider as &amp;quot;grounded&amp;quot;&lt;br /&gt;
        local rayStart = pos&lt;br /&gt;
        local rayEnd = pos + vector(0, 0, -rayDistance)&lt;br /&gt;
        local hitResult = ll.CastRay(rayStart, rayEnd, {})&lt;br /&gt;
        if table.getn(hitResult) == 3 then&lt;br /&gt;
            lastGrounded = true&lt;br /&gt;
        else&lt;br /&gt;
            lastGrounded = false&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return lastGrounded&lt;br /&gt;
end &lt;br /&gt;
&lt;br /&gt;
-- Timer event: updates engine, vehicle physics, steering, and rotates the wheels based on steering.&lt;br /&gt;
function timer()&lt;br /&gt;
    local dt = 0.1  -- time step in seconds&lt;br /&gt;
&lt;br /&gt;
    -- For neutral, update RPM based on throttle.&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        updateEngineRPMNeutral()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Calculate forces.&lt;br /&gt;
    local C_rr = (Engine.gear == 0) and C_rr_neutral or C_rr_inGear&lt;br /&gt;
    local F_roll = Vehicle.mass * G * C_rr&lt;br /&gt;
    local engineForce = getEngineForce()&lt;br /&gt;
    local dragForce = getAerodynamicDrag(math.abs(Vehicle.speed))&lt;br /&gt;
    local netForce = engineForce - (F_roll + dragForce)&lt;br /&gt;
&lt;br /&gt;
    -- Apply braking force if brakes are engaged.&lt;br /&gt;
    if Vehicle.brake then&lt;br /&gt;
        netForce = netForce - BRAKE_FORCE&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local acceleration = netForce / Vehicle.mass&lt;br /&gt;
    local vel = ll.GetVel()&lt;br /&gt;
    local horizontal_vec = vector(vel.x, vel.y, 0) &lt;br /&gt;
    Vehicle.speed = (ll.VecMag(horizontal_vec) + acceleration * dt)&lt;br /&gt;
&lt;br /&gt;
    -- Limit vehicle speed based on gear.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        if Vehicle.speed &amp;lt; 0 then &lt;br /&gt;
            Vehicle.speed = 0 &lt;br /&gt;
        end&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local maxSpeed = Engine.maxRPM / conversionFactor&lt;br /&gt;
        if Vehicle.speed &amp;gt; maxSpeed then&lt;br /&gt;
            Vehicle.speed = maxSpeed&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- When in gear, update RPM based on the new speed.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        Engine.rpm = computeEngineRPMFromSpeed()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Steering update using angular velocity and quaternions:&lt;br /&gt;
    local STEERING_RATE = math.rad(6)  -- desired steering rate per tick (radians)&lt;br /&gt;
    local yawDelta = Vehicle.steering * STEERING_RATE  -- small rotation for this tick&lt;br /&gt;
&lt;br /&gt;
    if yawDelta ~= 0 then&lt;br /&gt;
        local deltaQuat = ll.Euler2Rot(vector(0, 0, yawDelta))&lt;br /&gt;
        local angularSpeed = yawDelta / dt&lt;br /&gt;
        local angularVelocity = vector(0, 0, angularSpeed)&lt;br /&gt;
        ll.SetAngularVelocity(angularVelocity, 1)&lt;br /&gt;
        Vehicle.angle = Vehicle.angle + yawDelta&lt;br /&gt;
    else&lt;br /&gt;
        ll.SetAngularVelocity(vector(0, 0, 0), 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Display engine RPM and speed (converted to mph).&lt;br /&gt;
    local speed_mph = math.abs(Vehicle.speed) * 2.23694&lt;br /&gt;
    ll.SetText(&amp;quot;RPM: &amp;quot; .. math.floor(Engine.rpm) ..&lt;br /&gt;
               &amp;quot; | Speed: &amp;quot; .. string.format(&amp;quot;%.1f&amp;quot;, speed_mph) ..&lt;br /&gt;
               &amp;quot; mph&amp;quot;, vector(0,0,0), 1)&lt;br /&gt;
&lt;br /&gt;
    -- Only update velocity if the vehicle is near the ground, and we&#039;re in gear.&lt;br /&gt;
    if isVehicleOnGround() and Engine.gear ~= 0 then&lt;br /&gt;
        local velocityVector&lt;br /&gt;
        if Engine.gear &amp;gt; 0 then&lt;br /&gt;
            velocityVector = vector(Vehicle.speed, 0, 0)&lt;br /&gt;
        else&lt;br /&gt;
            -- Reverse gear&lt;br /&gt;
            velocityVector = vector(-Vehicle.speed, 0, 0)&lt;br /&gt;
        end&lt;br /&gt;
        ll.SetVelocity(velocityVector, 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Update the wheel rotations to visually match the steering.&lt;br /&gt;
    local maxWheelTurn = math.rad(30)  -- maximum wheel turn angle in radians&lt;br /&gt;
    local wheelAngle = -Vehicle.steering * maxWheelTurn&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_LEFT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_LEFT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(-4.71239, -wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_RIGHT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_RIGHT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(4.71239, wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched: starts engine and simulation.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    ll.Say(0, &amp;quot;Touch detected, requesting controls and starting engine.&amp;quot;)&lt;br /&gt;
    ll.RequestPermissions(ll.GetOwner(), PERMISSION_TAKE_CONTROLS)&lt;br /&gt;
    startEngine()&lt;br /&gt;
    findWheels()&lt;br /&gt;
    ll.SetTimerEvent(0.1)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when runtime permissions are granted.&lt;br /&gt;
function run_time_permissions(perm)&lt;br /&gt;
    if bit32.band(perm, PERMISSION_TAKE_CONTROLS) then&lt;br /&gt;
        ll.Say(0, &amp;quot;Permissions granted.&amp;quot;)&lt;br /&gt;
        local controlMask = bit32.bor(CONTROL_FWD, CONTROL_BACK, CONTROL_UP, CONTROL_DOWN, CONTROL_LEFT, CONTROL_RIGHT)&lt;br /&gt;
        ll.TakeControls(controlMask, 1, 1)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Handle control events:&lt;br /&gt;
-- • CONTROL_FWD applies throttle.&lt;br /&gt;
-- • CONTROL_BACK applies the brakes.&lt;br /&gt;
-- • CONTROL_UP and CONTROL_DOWN shift gears.&lt;br /&gt;
-- • CONTROL_LEFT and CONTROL_RIGHT steer the vehicle.&lt;br /&gt;
function control(avatar_id, level, edge)&lt;br /&gt;
    local start = bit32.band(level, edge)            &lt;br /&gt;
    local finish = bit32.band(bit32.bnot(level), edge) &lt;br /&gt;
    local held = bit32.band(level, bit32.bnot(edge))   &lt;br /&gt;
    local untouched = bit32.bnot(bit32.bor(level, edge)) &lt;br /&gt;
&lt;br /&gt;
    if bit32.band(held, CONTROL_BACK) ~= 0 then&lt;br /&gt;
        Vehicle.brake = true&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    elseif bit32.band(held, CONTROL_FWD) ~= 0 then&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 1.0&lt;br /&gt;
        if Engine.gear == 0 then&lt;br /&gt;
            updateEngineRPMNeutral()&lt;br /&gt;
        end&lt;br /&gt;
    else&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_UP) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear + 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_DOWN) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear - 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local steeringInput = 0&lt;br /&gt;
    if bit32.band(held, CONTROL_LEFT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput + 1&lt;br /&gt;
    end&lt;br /&gt;
    if bit32.band(held, CONTROL_RIGHT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput - 1&lt;br /&gt;
    end&lt;br /&gt;
    Vehicle.steering = steeringInput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== weather_box.lua ===&lt;br /&gt;
This script demonstrates coroutines, as well as working with a JSON-based web API:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local TEXTBOX_CHANNEL = 323242&lt;br /&gt;
-- kludge because we don&#039;t have LSL constants in here yet&lt;br /&gt;
local HTTP_BODY_MAXLENGTH = integer(2)&lt;br /&gt;
local PRIM_TEXT = integer(26)&lt;br /&gt;
local PRIM_SIZE = integer(7)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
gCityName = &amp;quot;&amp;quot;&lt;br /&gt;
gLatLong = nil  -- A table of 2 elems when it&#039;s populated&lt;br /&gt;
gCityRequestTask = nil&lt;br /&gt;
gWeatherRequestTask = nil&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- JSON stuff&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- From https://github.com/rxi/json.lua/blob/master/json.lua&lt;br /&gt;
&lt;br /&gt;
local json = {}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map = {&lt;br /&gt;
  [ &amp;quot;\\&amp;quot; ] = &amp;quot;\\&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\&amp;quot;&amp;quot; ] = &amp;quot;\&amp;quot;&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\b&amp;quot; ] = &amp;quot;b&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\f&amp;quot; ] = &amp;quot;f&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\n&amp;quot; ] = &amp;quot;n&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\r&amp;quot; ] = &amp;quot;r&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\t&amp;quot; ] = &amp;quot;t&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map_inv = { [ &amp;quot;/&amp;quot; ] = &amp;quot;/&amp;quot; }&lt;br /&gt;
for k, v in pairs(escape_char_map) do&lt;br /&gt;
  escape_char_map_inv[v] = k&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local parse&lt;br /&gt;
&lt;br /&gt;
local function create_set(...)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  for i = 1, select(&amp;quot;#&amp;quot;, ...) do&lt;br /&gt;
    res[ select(i, ...) ] = true&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local space_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;)&lt;br /&gt;
local delim_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, &amp;quot;]&amp;quot;, &amp;quot;}&amp;quot;, &amp;quot;,&amp;quot;)&lt;br /&gt;
local escape_chars  = create_set(&amp;quot;\\&amp;quot;, &amp;quot;/&amp;quot;, &#039;&amp;quot;&#039;, &amp;quot;b&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;r&amp;quot;, &amp;quot;t&amp;quot;, &amp;quot;u&amp;quot;)&lt;br /&gt;
local literals      = create_set(&amp;quot;true&amp;quot;, &amp;quot;false&amp;quot;, &amp;quot;null&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local literal_map = {&lt;br /&gt;
  [ &amp;quot;true&amp;quot;  ] = true,&lt;br /&gt;
  [ &amp;quot;false&amp;quot; ] = false,&lt;br /&gt;
  [ &amp;quot;null&amp;quot;  ] = nil,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function next_char(str, idx, set, negate)&lt;br /&gt;
  for i = idx, #str do&lt;br /&gt;
    if set[str:sub(i, i)] ~= negate then&lt;br /&gt;
      return i&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  return #str + 1&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function decode_error(str, idx, msg)&lt;br /&gt;
  local line_count = 1&lt;br /&gt;
  local col_count = 1&lt;br /&gt;
  for i = 1, idx - 1 do&lt;br /&gt;
    col_count = col_count + 1&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;\n&amp;quot; then&lt;br /&gt;
      line_count = line_count + 1&lt;br /&gt;
      col_count = 1&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;%s at line %d col %d&amp;quot;, msg, line_count, col_count) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function codepoint_to_utf8(n)&lt;br /&gt;
  -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&amp;amp;id=iws-appendixa&lt;br /&gt;
  local f = math.floor&lt;br /&gt;
  if n &amp;lt;= 0x7f then&lt;br /&gt;
    return string.char(n)&lt;br /&gt;
  elseif n &amp;lt;= 0x7ff then&lt;br /&gt;
    return string.char(f(n / 64) + 192, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0xffff then&lt;br /&gt;
    return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0x10ffff then&lt;br /&gt;
    return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,&lt;br /&gt;
                       f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;invalid unicode codepoint &#039;%x&#039;&amp;quot;, n) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_unicode_escape(s)&lt;br /&gt;
  local n1 = tonumber( s:sub(1, 4),  16 )&lt;br /&gt;
  local n2 = tonumber( s:sub(7, 10), 16 )&lt;br /&gt;
   -- Surrogate pair?&lt;br /&gt;
  if n2 then&lt;br /&gt;
    return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)&lt;br /&gt;
  else&lt;br /&gt;
    return codepoint_to_utf8(n1)&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_string(str, i)&lt;br /&gt;
  local res = &amp;quot;&amp;quot;&lt;br /&gt;
  local j = i + 1&lt;br /&gt;
  local k = j&lt;br /&gt;
&lt;br /&gt;
  while j &amp;lt;= #str do&lt;br /&gt;
    local x = str:byte(j)&lt;br /&gt;
&lt;br /&gt;
    if x &amp;lt; 32 then&lt;br /&gt;
      decode_error(str, j, &amp;quot;control character in string&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    elseif x == 92 then -- `\`: Escape&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      j = j + 1&lt;br /&gt;
      local c = str:sub(j, j)&lt;br /&gt;
      if c == &amp;quot;u&amp;quot; then&lt;br /&gt;
        local hex = str:match(&amp;quot;^[dD][89aAbB]%x%x\\u%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or str:match(&amp;quot;^%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or decode_error(str, j - 1, &amp;quot;invalid unicode escape in string&amp;quot;)&lt;br /&gt;
        res = res .. parse_unicode_escape(hex)&lt;br /&gt;
        j = j + #hex&lt;br /&gt;
      else&lt;br /&gt;
        if not escape_chars[c] then&lt;br /&gt;
          decode_error(str, j - 1, &amp;quot;invalid escape char &#039;&amp;quot; .. c .. &amp;quot;&#039; in string&amp;quot;)&lt;br /&gt;
        end&lt;br /&gt;
        res = res .. escape_char_map_inv[c]&lt;br /&gt;
      end&lt;br /&gt;
      k = j + 1&lt;br /&gt;
&lt;br /&gt;
    elseif x == 34 then -- `&amp;quot;`: End of string&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      return res, j + 1&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    j = j + 1&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  decode_error(str, i, &amp;quot;expected closing quote for string&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_number(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local s = str:sub(i, x - 1)&lt;br /&gt;
  local n = tonumber(s)&lt;br /&gt;
  if not n then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid number &#039;&amp;quot; .. s .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return n, x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_literal(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local word = str:sub(i, x - 1)&lt;br /&gt;
  if not literals[word] then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid literal &#039;&amp;quot; .. word .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return literal_map[word], x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_array(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  local n = 1&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local x&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of array?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;]&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read token&lt;br /&gt;
    x, i = parse(str, i)&lt;br /&gt;
    res[n] = x&lt;br /&gt;
    n = n + 1&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;]&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;]&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_object(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local key, val&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of object?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;}&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read key&lt;br /&gt;
    if str:sub(i, i) ~= &#039;&amp;quot;&#039; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected string for key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    key, i = parse(str, i)&lt;br /&gt;
    -- Read &#039;:&#039; delimiter&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    if str:sub(i, i) ~= &amp;quot;:&amp;quot; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected &#039;:&#039; after key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    i = next_char(str, i + 1, space_chars, true)&lt;br /&gt;
    -- Read value&lt;br /&gt;
    val, i = parse(str, i)&lt;br /&gt;
    -- Set&lt;br /&gt;
    res[key] = val&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;}&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;}&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local char_func_map = {&lt;br /&gt;
  [ &#039;&amp;quot;&#039; ] = parse_string,&lt;br /&gt;
  [ &amp;quot;0&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;1&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;2&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;3&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;4&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;5&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;6&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;7&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;8&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;9&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;-&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;t&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;f&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;n&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;[&amp;quot; ] = parse_array,&lt;br /&gt;
  [ &amp;quot;{&amp;quot; ] = parse_object,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
parse = function(str, idx)&lt;br /&gt;
  local chr = str:sub(idx, idx)&lt;br /&gt;
  local f = char_func_map[chr]&lt;br /&gt;
  if f then&lt;br /&gt;
    return f(str, idx)&lt;br /&gt;
  end&lt;br /&gt;
  decode_error(str, idx, &amp;quot;unexpected character &#039;&amp;quot; .. chr .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function json.decode(str)&lt;br /&gt;
  if type(str) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
    error(&amp;quot;expected argument of type string, got &amp;quot; .. type(str))&lt;br /&gt;
  end&lt;br /&gt;
  local res, idx = parse(str, next_char(str, 1, space_chars, true))&lt;br /&gt;
  idx = next_char(str, idx, space_chars, true)&lt;br /&gt;
  if idx &amp;lt;= #str then&lt;br /&gt;
    decode_error(str, idx, &amp;quot;trailing garbage&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Loop&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- A naive little event loop that lets you track running coroutines and dispatches&lt;br /&gt;
-- the events they&#039;re waiting for to them.&lt;br /&gt;
&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; awaited event&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    -- false has no semantic meaning here, we just want to reserve the space for&lt;br /&gt;
    -- the coroutine in the table without saying we&#039;re awaiting anything yet.&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- (internal) run the event&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    assert(coroutine.status(coro) ~= &amp;quot;dead&amp;quot;)&lt;br /&gt;
    -- assert(not self.running)&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    -- Since we&#039;re running, this isn&#039;t currently awaiting anything&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local success, ret = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    -- If we&#039;re not done then ret should be the kind of event we&#039;re awaiting.&lt;br /&gt;
    -- Otherwise it&#039;s an error message or something. Who cares.&lt;br /&gt;
    if not success then&lt;br /&gt;
        -- Get rid of the coro&lt;br /&gt;
        if ret then&lt;br /&gt;
            ll.OwnerSay(`Might have an error: {ret}`)&lt;br /&gt;
        end&lt;br /&gt;
        ret = nil&lt;br /&gt;
    end&lt;br /&gt;
    self._coros[coro] = ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Wake up any tasks that are waiting for this kind of event&lt;br /&gt;
function EventLoop:handle_event(name, ...)&lt;br /&gt;
    -- We may mutate self._coros, so do a clone&lt;br /&gt;
    local coros = table.clone(self._coros)&lt;br /&gt;
    ll.OwnerSay(`Handling {name}`)&lt;br /&gt;
&lt;br /&gt;
    for coro, awaited_event in coros do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            -- Hmm, this coroutine is dead, prune it.&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if awaited_event == name then&lt;br /&gt;
            ll.OwnerSay(`Dispatching {name} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_event_catcher(name)&lt;br /&gt;
    local function catcher(...)&lt;br /&gt;
        ll.OwnerSay(&amp;quot;yay&amp;quot;)&lt;br /&gt;
        EventLoop:handle_event(name, ...)&lt;br /&gt;
    end&lt;br /&gt;
    return catcher&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Helper function for waiting and telling the EventLoop what event you want&lt;br /&gt;
local function await_event(kind)&lt;br /&gt;
    -- You had better do this inside a running event loop&lt;br /&gt;
    assert(EventLoop.running)&lt;br /&gt;
    -- This will yield, the EventLoop will resume us once it has&lt;br /&gt;
    -- an event we might be interested in.&lt;br /&gt;
    return coroutine.yield(kind)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Script-specific functions&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
local function hide_children()&lt;br /&gt;
    for i=2,ll.GetNumberOfPrims() do&lt;br /&gt;
        -- Hide all the child prims&lt;br /&gt;
        ll.SetLinkAlpha(i, 0, -1)&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(i, {&lt;br /&gt;
            PRIM_SIZE, vector(0.5, 0.5, 0.5),&lt;br /&gt;
            PRIM_TEXT, &amp;quot;&amp;quot;, vector(1,1,1), 1&lt;br /&gt;
        })&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_textbox(avatar_id, message, channel)&lt;br /&gt;
    ll.TextBox(avatar_id, message, channel)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        local resp_channel, name, id, resp = await_event(&amp;quot;listen&amp;quot;)&lt;br /&gt;
        if id ~= avatar_id or channel ~= resp_channel then&lt;br /&gt;
            -- If this isn&#039;t a message we&#039;re interested in then wait for the next event&lt;br /&gt;
            ll.OwnerSay(`{id}, {avatar_id}, {resp_channel}, {channel}`)&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return resp&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function make_json_request(url)&lt;br /&gt;
    local req_id = ll.HTTPRequest(url, {HTTP_BODY_MAXLENGTH, integer(16000)}, &amp;quot;&amp;quot;)&lt;br /&gt;
    while true do&lt;br /&gt;
        local ev_id, status, metadata, body = await_event(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
        if ev_id ~= req_id then&lt;br /&gt;
            -- Not a response for the HTTP request we were waiting on, wait for the next event.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return status, metadata, body&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function request_weather()&lt;br /&gt;
    -- And you can use a wrapper like this to make an async function callable within both normal and coroutine contexts.&lt;br /&gt;
    -- Normally this sort of thing would not be necessary, but here it is since the EventLoop lives in our own code,&lt;br /&gt;
    -- and isn&#039;t a property of the actual script engine.&lt;br /&gt;
    if gWeatherRequestTask then&lt;br /&gt;
        EventLoop:kill_task(gWeatherRequestTask)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    gWeatherRequestTask = EventLoop:create_task(function()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Requesting weather&amp;quot;)&lt;br /&gt;
        local status, metadata, body = make_json_request(`https://api.open-meteo.com/v1/forecast?latitude={gLatLong[1]}&amp;amp;longitude={gLatLong[2]}&amp;amp;current=temperature_2m&amp;amp;daily=temperature_2m_max`)&lt;br /&gt;
        if status ~= 200 then&lt;br /&gt;
            ll.OwnerSay(&amp;quot;Error requesting weather&amp;quot;)&lt;br /&gt;
            return&lt;br /&gt;
        end&lt;br /&gt;
        local parsed = json.decode(body)&lt;br /&gt;
&lt;br /&gt;
        local current = parsed[&amp;quot;current&amp;quot;]&lt;br /&gt;
        ll.OwnerSay(`Temperature is {current[&amp;quot;temperature_2m&amp;quot;]}C`)&lt;br /&gt;
        ll.SetText(`Temperature in {gCityName} is currently {current[&amp;quot;temperature_2m&amp;quot;]}C`, vector(1,1,1), 1)&lt;br /&gt;
&lt;br /&gt;
        -- Show the highs for each day&lt;br /&gt;
        local days = parsed[&amp;quot;daily&amp;quot;][&amp;quot;temperature_2m_max&amp;quot;]&lt;br /&gt;
        for i=1,#days do&lt;br /&gt;
            local link_num = i + 1&lt;br /&gt;
            -- Higher temps = bigger on Z, negatives are unrepresentable :)&lt;br /&gt;
            local z_size = (days[i] / 30) * 2&lt;br /&gt;
            ll.SetLinkAlpha(link_num, 1, -1)&lt;br /&gt;
            ll.SetLinkPrimitiveParamsFast(link_num, {&lt;br /&gt;
                PRIM_SIZE, vector(0.5, 0.5, z_size),&lt;br /&gt;
                PRIM_TEXT, `{days[i]}C`, vector(1,1,1), 1&lt;br /&gt;
            })&lt;br /&gt;
        end&lt;br /&gt;
    end)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ask_for_city()&lt;br /&gt;
    local resp = send_textbox(ll.GetOwner(), &amp;quot;What city do you want the weather for?&amp;quot;, TEXTBOX_CHANNEL)&lt;br /&gt;
    if not resp then&lt;br /&gt;
        ll.OwnerSay(&amp;quot;Got no response for city request.&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    ll.OwnerSay(`You want the weather for {resp}.`)&lt;br /&gt;
    gLatLong = nil&lt;br /&gt;
    gCityName = resp&lt;br /&gt;
&lt;br /&gt;
    hide_children()&lt;br /&gt;
&lt;br /&gt;
    -- request the lat and long for this city&lt;br /&gt;
    local status, metadata, body = make_json_request(`https://nominatim.openstreetmap.org/search.php?city={ll.EscapeURL(resp)}&amp;amp;format=jsonv2`)&lt;br /&gt;
    local parsed = json.decode(body)&lt;br /&gt;
    if not #parsed then&lt;br /&gt;
        ll.OwnerSay(`Got no results for {resp}!`)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local first_result = parsed[1]&lt;br /&gt;
    gLatLong = {first_result[&amp;quot;lat&amp;quot;], first_result[&amp;quot;lon&amp;quot;]}&lt;br /&gt;
    ll.OwnerSay(`City Lat: {gLatLong[1]}, Long: {gLatLong[2]}`)&lt;br /&gt;
&lt;br /&gt;
    request_weather()&lt;br /&gt;
&lt;br /&gt;
    -- request weather again in 3 mins&lt;br /&gt;
    ll.SetTimerEvent(180)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Handlers&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- If we don&#039;t care about conventionally dispatched events, we can just set the event handler to directly send&lt;br /&gt;
-- event data to the event loop.&lt;br /&gt;
http_response = EventLoop:create_event_catcher(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
listen = EventLoop:create_event_catcher(&amp;quot;listen&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- More conventional event handlers work too.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    for i=0,num-1 do&lt;br /&gt;
        if ll.GetOwner() ~= ll.DetectedKey(i) then&lt;br /&gt;
            -- Go to next loop if it wasn&#039;t the owner touching us.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if gCityRequestTask then&lt;br /&gt;
            EventLoop:kill_task(gCityRequestTask)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Spin up a task to ask the user for their city&lt;br /&gt;
        gCityRequestTask = EventLoop:create_task(ask_for_city)&lt;br /&gt;
        break&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function timer()&lt;br /&gt;
    request_weather()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Not strictly necessary, but I like having the main function have its own scope.&lt;br /&gt;
-- Rather than just have top-level logic.&lt;br /&gt;
local function main()&lt;br /&gt;
    ll.Listen(TEXTBOX_CHANNEL, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.SetText(&amp;quot;&amp;quot;, vector(1,1,1), 1)&lt;br /&gt;
    hide_children()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Weatherbox operational!&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
main()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218006</id>
		<title>Lua Alpha</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218006"/>
		<updated>2025-03-04T22:43:13Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:Luau.png|720px|thumb|right|Luau logo]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Warning|This functioanlity 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.}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Second Life Luau Alpha =&lt;br /&gt;
&lt;br /&gt;
We&#039;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.&lt;br /&gt;
&lt;br /&gt;
== What is Luau? ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Why Luau? ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== How to Get Started with Luau in Second Life ==&lt;br /&gt;
&lt;br /&gt;
In order to play with Luau, you&#039;ll need to download our Lua project viewer, and log onto our Aditi beta grid.&lt;br /&gt;
&lt;br /&gt;
* Access the latest build of the Luau-enabled Second Life Viewer from [https://releasenotes.secondlife.com/viewer/7.1.12.13526902562.html here].&lt;br /&gt;
&lt;br /&gt;
Once you&#039;ve got the new viewer and have logged onto the beta grid, head over to these Luau-enabled regions:&lt;br /&gt;
* Luau Yardang&lt;br /&gt;
* Luau Tombolo&lt;br /&gt;
* Luau Mesa&lt;br /&gt;
* Luau Tideland&lt;br /&gt;
&lt;br /&gt;
When editing a script in the new Lua project viewer, you&#039;ll notice a new &#039;&#039;&#039;Compiler&#039;&#039;&#039; 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).&lt;br /&gt;
&lt;br /&gt;
[[File:Compiler_dropdown.png|Compiler selection dropdown]]&lt;br /&gt;
&lt;br /&gt;
Compiler drop-down options:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;LSL: Legacy (LSO2)&#039;&#039;&#039; - Scripts written in LSL, to be run on the old LSO2 VM&lt;br /&gt;
* &#039;&#039;&#039;LSL: Mono&#039;&#039;&#039;- Scripts written in LSL, to be run on the Mono VM&lt;br /&gt;
* &#039;&#039;&#039;Lua&#039;&#039;&#039; - Scripts written in Lua, to be run on the Luau VM&lt;br /&gt;
* &#039;&#039;&#039;LSL/Luau&#039;&#039;&#039;- Scripts written in LSL, to be run on the Luau VM&lt;br /&gt;
&lt;br /&gt;
=== Transitioning from LSL to Luau ===&lt;br /&gt;
* &#039;&#039;&#039;Function Namespacing:&#039;&#039;&#039;&lt;br /&gt;
** In Luau, Linden Lab functions have been moved under the &#039;&#039;&#039;ll&#039;&#039;&#039; namespace.&lt;br /&gt;
** For example:&lt;br /&gt;
*** &#039;&#039;llSay&#039;&#039; becomes &#039;&#039;ll.Say&#039;&#039;&lt;br /&gt;
*** &#039;&#039;llGetPos&#039;&#039; becomes &#039;&#039;ll.GetPos&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Constants:&#039;&#039;&#039;&lt;br /&gt;
** Constants like &#039;&#039;PERMISSION_TAKE_CONTROLS&#039;&#039; and &#039;&#039;PRIM_ROT_LOCAL&#039;&#039; are not predefined in Luau.&lt;br /&gt;
** You must specify these constants if you wish to reference them in your scripts.&lt;br /&gt;
&lt;br /&gt;
=== Luau Libraries ===&lt;br /&gt;
* &#039;&#039;&#039;Coroutines:&#039;&#039;&#039;&lt;br /&gt;
** Luau supports coroutines, allowing for cooperative multitasking within scripts.&lt;br /&gt;
** Key functions include:&lt;br /&gt;
*** &#039;&#039;coroutine.create&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.status&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.resume&#039;&#039;&lt;br /&gt;
** Refer to the [https://luau.org/library#coroutine-library coroutine library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Bitwise Operations:&#039;&#039;&#039;&lt;br /&gt;
** Luau includes a &#039;&#039;bit32&#039;&#039; library for bitwise operations, enabling more efficient data manipulation.&lt;br /&gt;
** Refer to the [https://luau.org/library#bit32-library bit32 library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Standard Library:&#039;&#039;&#039;&lt;br /&gt;
** Luau comes equipped with a standard library of functions designed to manipulate built-in data types.&lt;br /&gt;
** Explore the [https://luau.org/library Luau Standard Library] for a comprehensive list of available functions.&lt;br /&gt;
&lt;br /&gt;
== Feedback and Support ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== Example Scripts ==&lt;br /&gt;
&lt;br /&gt;
To help you get started, we&#039;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&#039;s desire!&lt;br /&gt;
&lt;br /&gt;
=== dialog.lua ===&lt;br /&gt;
This script demonstrates how one can interact with dialog menus.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Define the menu buttons and dialog message.&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local ToucherID = nil&lt;br /&gt;
local dialogChannel = nil&lt;br /&gt;
local listenHandle = nil&lt;br /&gt;
&lt;br /&gt;
-- This function is called when the script first starts.&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Get the object&#039;s key and compute a dialog channel number.&lt;br /&gt;
    local key = ll.GetKey()&lt;br /&gt;
    -- Extract the last 7 characters of the key and convert it from hex.&lt;br /&gt;
    dialogChannel = -1 - tonumber(string.sub(tostring(key), -7, -1), 16)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    ToucherID = ll.DetectedKey(0)&lt;br /&gt;
    -- If there is already a listen handle, then remove it&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
    end&lt;br /&gt;
    listenHandle = ll.Listen(dialogChannel, &amp;quot;&amp;quot;, ToucherID, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
    -- Set a 60-second timer for response.&lt;br /&gt;
    ll.SetTimerEvent(60.0)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when a dialog response is received.&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    if message == &amp;quot;-&amp;quot; then&lt;br /&gt;
        -- Redisplay the dialog if the &amp;quot;-&amp;quot; option is selected.&lt;br /&gt;
        ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Stop the timer, and stop the listening handler.&lt;br /&gt;
    ll.ListenRemove(listenHandle)&lt;br /&gt;
    ll.SetTimerEvent(0)&lt;br /&gt;
    -- Let the user know what they selected&lt;br /&gt;
    ll.Say(0, `You selected {message}`)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the timer expires.&lt;br /&gt;
function timer()&lt;br /&gt;
    -- Stop the timer and clean up the listener.&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.SetTimerEvent(0)&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
        ll.Whisper(0, &amp;quot;Sorry. You snooze; you lose.&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dialog_coroutine.lua ===&lt;br /&gt;
This script demonstrates how one could use coroutines to handle dialog responses, with multi-user support.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Minimal EventLoop&lt;br /&gt;
-------------------------&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; eventName it’s waiting for&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status(coro) ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Internal helper: resumes a coroutine&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local ok, eventAwaited = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    if not ok then&lt;br /&gt;
        ll.OwnerSay(`Coroutine error: {eventAwaited}`)&lt;br /&gt;
        self._coros[coro] = nil&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- If still alive, &#039;eventAwaited&#039; is the next event it wants&lt;br /&gt;
    self._coros[coro] = eventAwaited&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:handle_event(eventName, ...)&lt;br /&gt;
    ll.OwnerSay(`Handling event {eventName}`)&lt;br /&gt;
    local snapshot = table.clone(self._coros)&lt;br /&gt;
    for coro, waitingFor in pairs(snapshot) do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        elseif waitingFor == eventName then&lt;br /&gt;
            ll.OwnerSay(`Dispatching event {eventName} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Coroutines use this to yield until an event&lt;br /&gt;
local function await_event(name)&lt;br /&gt;
    assert(EventLoop.running, &amp;quot;await_event called outside a coroutine!&amp;quot;)&lt;br /&gt;
    return coroutine.yield(name)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Script Logic&lt;br /&gt;
-------------------------&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- Use the chat listener to feed the event-loop&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    -- We handle all &#039;listen&#039; events via the event-loop&lt;br /&gt;
    EventLoop:handle_event(`listen_{channel}`, channel, name, sender_id, message)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the script starts&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Seed math.random so each new script run doesn’t repeat the same channels&lt;br /&gt;
    math.randomseed(ll.GetUnixTime())&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Script started with random channels for each user.&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- A coroutine function for a single user&#039;s dialog flow&lt;br /&gt;
local function handle_dialog_for_user(userId)&lt;br /&gt;
    -- Use a random channel&lt;br /&gt;
    local channel = math.random(0x1, 0xFFFF)&lt;br /&gt;
    -- Create a listener for that channel&lt;br /&gt;
    local listenHandle = ll.Listen(channel, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        -- Show the user a dialog&lt;br /&gt;
        ll.Dialog(userId, dialogInfo, buttons, channel)&lt;br /&gt;
&lt;br /&gt;
        -- Wait for the next &#039;listen&#039; event (channel, name, sender_id, message)&lt;br /&gt;
        local c, n, sid, msg = await_event(`listen_{channel}`)&lt;br /&gt;
&lt;br /&gt;
        -- If this &amp;quot;listen&amp;quot; event isn&#039;t for our channel/user, ignore it&lt;br /&gt;
        if c == channel and sid == userId then&lt;br /&gt;
            -- If user pressed &amp;quot;-&amp;quot;, re-display the menu, else they picked a final color&lt;br /&gt;
            if msg ~= &amp;quot;-&amp;quot; then&lt;br /&gt;
                ll.Say(0, `{n} selected {msg}`)&lt;br /&gt;
                -- Now that they&#039;ve chosen something else, remove the listener and finish&lt;br /&gt;
                ll.ListenRemove(listenHandle)&lt;br /&gt;
                return&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    for i=0, num_detected-1 do&lt;br /&gt;
        local toucherId = ll.DetectedKey(i)&lt;br /&gt;
        -- Create a separate coroutine for each person who touches&lt;br /&gt;
        EventLoop:create_task(function()&lt;br /&gt;
            handle_dialog_for_user(toucherId)&lt;br /&gt;
        end)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Run state_entry on load:&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== vehicle.lua ===&lt;br /&gt;
This script demonstrates how one may create their own vehicle scripts, which don’t rely on the underlying LL vehicle system. &lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Control and permission constants&lt;br /&gt;
local PERMISSION_TAKE_CONTROLS = 0x4&lt;br /&gt;
local CONTROL_FWD       = 0x1&lt;br /&gt;
local CONTROL_BACK      = 0x2&lt;br /&gt;
local CONTROL_UP        = 0x10&lt;br /&gt;
local CONTROL_DOWN      = 0x20&lt;br /&gt;
local CONTROL_LEFT      = 0x4&lt;br /&gt;
local CONTROL_RIGHT     = 0x8&lt;br /&gt;
local PRIM_ROT_LOCAL    = integer(29)&lt;br /&gt;
&lt;br /&gt;
-- These two will hold the link numbers for left and right front wheels&lt;br /&gt;
local LINK_WHEEL_LEFT = 0&lt;br /&gt;
local LINK_WHEEL_RIGHT = 0&lt;br /&gt;
&lt;br /&gt;
-- Integra Type R Engine properties&lt;br /&gt;
local Engine = {&lt;br /&gt;
    rpm = 800,              -- current engine RPM (starts at idle)&lt;br /&gt;
    gear = 0,               -- 0 = neutral; -1 = reverse; 1-5 for forward gears&lt;br /&gt;
    idleRPM = 800,          -- idle RPM&lt;br /&gt;
    maxRPM = 8300,          -- redline&lt;br /&gt;
    throttle = 0.0,         -- throttle value (0.0 to 1.0)&lt;br /&gt;
    gearRatios = {          -- Integra Type R gear ratios&lt;br /&gt;
        [-1] = 3.727,       -- reverse gear ratio&lt;br /&gt;
        [0]  = 0,          -- neutral&lt;br /&gt;
        [1]  = 3.727,&lt;br /&gt;
        [2]  = 2.256,&lt;br /&gt;
        [3]  = 1.729,&lt;br /&gt;
        [4]  = 1.416,&lt;br /&gt;
        [5]  = 1.194&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FINAL_DRIVE = 4.1   -- Final drive ratio&lt;br /&gt;
local WHEEL_RADIUS = 0.3  -- in meters&lt;br /&gt;
&lt;br /&gt;
-- Vehicle properties (using realistic Integra Type R mass)&lt;br /&gt;
local Vehicle = {&lt;br /&gt;
    speed = 0,      -- speed in m/s; negative indicates reverse motion&lt;br /&gt;
    mass = 1200,    -- mass in kg&lt;br /&gt;
    angle = 0,      -- heading angle in radians&lt;br /&gt;
    steering = 0,   -- steering input: -1 (left), 0 (none), 1 (right)&lt;br /&gt;
    brake = false   -- flag indicating if brakes are applied&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Engine constants (using realistic Integra Type R values)&lt;br /&gt;
local MAX_TORQUE = 200   -- in Nm&lt;br /&gt;
&lt;br /&gt;
-- Brake force constant (for active braking)&lt;br /&gt;
local BRAKE_FORCE = 4000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Define a torque curve function:&lt;br /&gt;
local function torqueCurve(rpm)&lt;br /&gt;
    local peakRPM = 6000&lt;br /&gt;
    if rpm &amp;lt; peakRPM then&lt;br /&gt;
        return 0.8 + 0.2 * ((rpm - Engine.idleRPM) / (peakRPM - Engine.idleRPM))&lt;br /&gt;
    else&lt;br /&gt;
        local factor = 1 - 0.5 * ((rpm - peakRPM) / (Engine.maxRPM - peakRPM))&lt;br /&gt;
        return math.max(0.5, factor)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Engine braking force when throttle is zero in gear.&lt;br /&gt;
local ENGINE_BRAKE_FORCE = 2000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Friction and drag constants&lt;br /&gt;
local G = 9.81            -- gravity (m/s^2)&lt;br /&gt;
local C_rr_inGear = 0.015 -- rolling resistance coefficient in gear&lt;br /&gt;
local C_rr_neutral = 0.05 -- rolling resistance coefficient in neutral&lt;br /&gt;
&lt;br /&gt;
local airDensity = 1.225      -- kg/m^3&lt;br /&gt;
local dragCoefficient = 0.3   -- aerodynamic drag coefficient&lt;br /&gt;
local frontalArea = 2.2       -- in m^2&lt;br /&gt;
&lt;br /&gt;
local function getAerodynamicDrag(speed)&lt;br /&gt;
    return 0.5 * airDensity * dragCoefficient * frontalArea * speed * speed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Compute engine RPM from vehicle speed when in gear.&lt;br /&gt;
local function computeEngineRPMFromSpeed()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return Engine.rpm  -- In neutral, RPM is managed independently.&lt;br /&gt;
    else&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local rpm = math.abs(Vehicle.speed) * conversionFactor&lt;br /&gt;
        if rpm &amp;lt; Engine.idleRPM then rpm = Engine.idleRPM end&lt;br /&gt;
        return rpm&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- For neutral, update RPM toward a target based on throttle.&lt;br /&gt;
local function updateEngineRPMNeutral()&lt;br /&gt;
    local targetRPM = Engine.idleRPM + (Engine.maxRPM - Engine.idleRPM) * Engine.throttle&lt;br /&gt;
    local changeRate = 200  -- RPM per tick&lt;br /&gt;
    if Engine.rpm &amp;lt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.min(Engine.rpm + changeRate, targetRPM)&lt;br /&gt;
    elseif Engine.rpm &amp;gt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.max(Engine.rpm - changeRate, targetRPM)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Calculate the engine force delivered to the wheels.&lt;br /&gt;
local function getEngineForce()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return 0  -- In neutral, no drive force is transmitted.&lt;br /&gt;
    else&lt;br /&gt;
        if Engine.throttle == 0 then&lt;br /&gt;
            -- Apply engine braking when throttle is released.&lt;br /&gt;
            return -ENGINE_BRAKE_FORCE&lt;br /&gt;
        else&lt;br /&gt;
            local torque = Engine.throttle * MAX_TORQUE * torqueCurve(Engine.rpm)&lt;br /&gt;
            local force = (torque * Engine.gearRatios[Engine.gear] * FINAL_DRIVE) / WHEEL_RADIUS&lt;br /&gt;
            return force&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Starts the engine by setting it to idle RPM.&lt;br /&gt;
local function startEngine()&lt;br /&gt;
    Engine.rpm = Engine.idleRPM&lt;br /&gt;
    ll.Say(0, &amp;quot;Engine started. RPM: &amp;quot; .. Engine.rpm)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Shifts the gear if valid.&lt;br /&gt;
local function shiftGear(newGear)&lt;br /&gt;
    if Engine.gearRatios[newGear] then&lt;br /&gt;
        Engine.gear = newGear&lt;br /&gt;
        ll.Say(0, &amp;quot;Shifted to gear &amp;quot; .. newGear)&lt;br /&gt;
    else&lt;br /&gt;
        ll.Say(0, &amp;quot;Invalid gear: &amp;quot; .. newGear)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function findWheels()&lt;br /&gt;
    local total = ll.GetNumberOfPrims()&lt;br /&gt;
    for i = 1, total do&lt;br /&gt;
        local name = ll.GetLinkName(i)&lt;br /&gt;
        if name == &amp;quot;WHEEL_LF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_LEFT = i&lt;br /&gt;
        elseif name == &amp;quot;WHEEL_RF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_RIGHT = i&lt;br /&gt;
        end&lt;br /&gt;
    end &lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local castRayCounter = 0&lt;br /&gt;
local lastGrounded = false&lt;br /&gt;
&lt;br /&gt;
local function isVehicleOnGround()&lt;br /&gt;
    castRayCounter = castRayCounter + 1&lt;br /&gt;
    if castRayCounter &amp;gt;= 5 then&lt;br /&gt;
        castRayCounter = 0&lt;br /&gt;
        local pos = ll.GetPos()&lt;br /&gt;
        local rayDistance = 4.5  -- threshold distance (in meters) to consider as &amp;quot;grounded&amp;quot;&lt;br /&gt;
        local rayStart = pos&lt;br /&gt;
        local rayEnd = pos + vector(0, 0, -rayDistance)&lt;br /&gt;
        local hitResult = ll.CastRay(rayStart, rayEnd, {})&lt;br /&gt;
        if table.getn(hitResult) == 3 then&lt;br /&gt;
            lastGrounded = true&lt;br /&gt;
        else&lt;br /&gt;
            lastGrounded = false&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return lastGrounded&lt;br /&gt;
end &lt;br /&gt;
&lt;br /&gt;
-- Timer event: updates engine, vehicle physics, steering, and rotates the wheels based on steering.&lt;br /&gt;
function timer()&lt;br /&gt;
    local dt = 0.1  -- time step in seconds&lt;br /&gt;
&lt;br /&gt;
    -- For neutral, update RPM based on throttle.&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        updateEngineRPMNeutral()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Calculate forces.&lt;br /&gt;
    local C_rr = (Engine.gear == 0) and C_rr_neutral or C_rr_inGear&lt;br /&gt;
    local F_roll = Vehicle.mass * G * C_rr&lt;br /&gt;
    local engineForce = getEngineForce()&lt;br /&gt;
    local dragForce = getAerodynamicDrag(math.abs(Vehicle.speed))&lt;br /&gt;
    local netForce = engineForce - (F_roll + dragForce)&lt;br /&gt;
&lt;br /&gt;
    -- Apply braking force if brakes are engaged.&lt;br /&gt;
    if Vehicle.brake then&lt;br /&gt;
        netForce = netForce - BRAKE_FORCE&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local acceleration = netForce / Vehicle.mass&lt;br /&gt;
    local vel = ll.GetVel()&lt;br /&gt;
    local horizontal_vec = vector(vel.x, vel.y, 0) &lt;br /&gt;
    Vehicle.speed = (ll.VecMag(horizontal_vec) + acceleration * dt)&lt;br /&gt;
&lt;br /&gt;
    -- Limit vehicle speed based on gear.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        if Vehicle.speed &amp;lt; 0 then &lt;br /&gt;
            Vehicle.speed = 0 &lt;br /&gt;
        end&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local maxSpeed = Engine.maxRPM / conversionFactor&lt;br /&gt;
        if Vehicle.speed &amp;gt; maxSpeed then&lt;br /&gt;
            Vehicle.speed = maxSpeed&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- When in gear, update RPM based on the new speed.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        Engine.rpm = computeEngineRPMFromSpeed()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Steering update using angular velocity and quaternions:&lt;br /&gt;
    local STEERING_RATE = math.rad(6)  -- desired steering rate per tick (radians)&lt;br /&gt;
    local yawDelta = Vehicle.steering * STEERING_RATE  -- small rotation for this tick&lt;br /&gt;
&lt;br /&gt;
    if yawDelta ~= 0 then&lt;br /&gt;
        local deltaQuat = ll.Euler2Rot(vector(0, 0, yawDelta))&lt;br /&gt;
        local angularSpeed = yawDelta / dt&lt;br /&gt;
        local angularVelocity = vector(0, 0, angularSpeed)&lt;br /&gt;
        ll.SetAngularVelocity(angularVelocity, 1)&lt;br /&gt;
        Vehicle.angle = Vehicle.angle + yawDelta&lt;br /&gt;
    else&lt;br /&gt;
        ll.SetAngularVelocity(vector(0, 0, 0), 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Display engine RPM and speed (converted to mph).&lt;br /&gt;
    local speed_mph = math.abs(Vehicle.speed) * 2.23694&lt;br /&gt;
    ll.SetText(&amp;quot;RPM: &amp;quot; .. math.floor(Engine.rpm) ..&lt;br /&gt;
               &amp;quot; | Speed: &amp;quot; .. string.format(&amp;quot;%.1f&amp;quot;, speed_mph) ..&lt;br /&gt;
               &amp;quot; mph&amp;quot;, vector(0,0,0), 1)&lt;br /&gt;
&lt;br /&gt;
    -- Only update velocity if the vehicle is near the ground, and we&#039;re in gear.&lt;br /&gt;
    if isVehicleOnGround() and Engine.gear ~= 0 then&lt;br /&gt;
        local velocityVector&lt;br /&gt;
        if Engine.gear &amp;gt; 0 then&lt;br /&gt;
            velocityVector = vector(Vehicle.speed, 0, 0)&lt;br /&gt;
        else&lt;br /&gt;
            -- Reverse gear&lt;br /&gt;
            velocityVector = vector(-Vehicle.speed, 0, 0)&lt;br /&gt;
        end&lt;br /&gt;
        ll.SetVelocity(velocityVector, 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Update the wheel rotations to visually match the steering.&lt;br /&gt;
    local maxWheelTurn = math.rad(30)  -- maximum wheel turn angle in radians&lt;br /&gt;
    local wheelAngle = -Vehicle.steering * maxWheelTurn&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_LEFT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_LEFT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(-4.71239, -wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_RIGHT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_RIGHT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(4.71239, wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched: starts engine and simulation.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    ll.Say(0, &amp;quot;Touch detected, requesting controls and starting engine.&amp;quot;)&lt;br /&gt;
    ll.RequestPermissions(ll.GetOwner(), PERMISSION_TAKE_CONTROLS)&lt;br /&gt;
    startEngine()&lt;br /&gt;
    findWheels()&lt;br /&gt;
    ll.SetTimerEvent(0.1)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when runtime permissions are granted.&lt;br /&gt;
function run_time_permissions(perm)&lt;br /&gt;
    if bit32.band(perm, PERMISSION_TAKE_CONTROLS) then&lt;br /&gt;
        ll.Say(0, &amp;quot;Permissions granted.&amp;quot;)&lt;br /&gt;
        local controlMask = bit32.bor(CONTROL_FWD, CONTROL_BACK, CONTROL_UP, CONTROL_DOWN, CONTROL_LEFT, CONTROL_RIGHT)&lt;br /&gt;
        ll.TakeControls(controlMask, 1, 1)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Handle control events:&lt;br /&gt;
-- • CONTROL_FWD applies throttle.&lt;br /&gt;
-- • CONTROL_BACK applies the brakes.&lt;br /&gt;
-- • CONTROL_UP and CONTROL_DOWN shift gears.&lt;br /&gt;
-- • CONTROL_LEFT and CONTROL_RIGHT steer the vehicle.&lt;br /&gt;
function control(avatar_id, level, edge)&lt;br /&gt;
    local start = bit32.band(level, edge)            &lt;br /&gt;
    local finish = bit32.band(bit32.bnot(level), edge) &lt;br /&gt;
    local held = bit32.band(level, bit32.bnot(edge))   &lt;br /&gt;
    local untouched = bit32.bnot(bit32.bor(level, edge)) &lt;br /&gt;
&lt;br /&gt;
    if bit32.band(held, CONTROL_BACK) ~= 0 then&lt;br /&gt;
        Vehicle.brake = true&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    elseif bit32.band(held, CONTROL_FWD) ~= 0 then&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 1.0&lt;br /&gt;
        if Engine.gear == 0 then&lt;br /&gt;
            updateEngineRPMNeutral()&lt;br /&gt;
        end&lt;br /&gt;
    else&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_UP) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear + 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_DOWN) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear - 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local steeringInput = 0&lt;br /&gt;
    if bit32.band(held, CONTROL_LEFT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput + 1&lt;br /&gt;
    end&lt;br /&gt;
    if bit32.band(held, CONTROL_RIGHT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput - 1&lt;br /&gt;
    end&lt;br /&gt;
    Vehicle.steering = steeringInput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== weather_box.lua ===&lt;br /&gt;
This script demonstrates coroutines, as well as working with a JSON-based web API:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local TEXTBOX_CHANNEL = 323242&lt;br /&gt;
-- kludge because we don&#039;t have LSL constants in here yet&lt;br /&gt;
local HTTP_BODY_MAXLENGTH = integer(2)&lt;br /&gt;
local PRIM_TEXT = integer(26)&lt;br /&gt;
local PRIM_SIZE = integer(7)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
gCityName = &amp;quot;&amp;quot;&lt;br /&gt;
gLatLong = nil  -- A table of 2 elems when it&#039;s populated&lt;br /&gt;
gCityRequestTask = nil&lt;br /&gt;
gWeatherRequestTask = nil&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- JSON stuff&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- From https://github.com/rxi/json.lua/blob/master/json.lua&lt;br /&gt;
&lt;br /&gt;
local json = {}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map = {&lt;br /&gt;
  [ &amp;quot;\\&amp;quot; ] = &amp;quot;\\&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\&amp;quot;&amp;quot; ] = &amp;quot;\&amp;quot;&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\b&amp;quot; ] = &amp;quot;b&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\f&amp;quot; ] = &amp;quot;f&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\n&amp;quot; ] = &amp;quot;n&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\r&amp;quot; ] = &amp;quot;r&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\t&amp;quot; ] = &amp;quot;t&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map_inv = { [ &amp;quot;/&amp;quot; ] = &amp;quot;/&amp;quot; }&lt;br /&gt;
for k, v in pairs(escape_char_map) do&lt;br /&gt;
  escape_char_map_inv[v] = k&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local parse&lt;br /&gt;
&lt;br /&gt;
local function create_set(...)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  for i = 1, select(&amp;quot;#&amp;quot;, ...) do&lt;br /&gt;
    res[ select(i, ...) ] = true&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local space_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;)&lt;br /&gt;
local delim_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, &amp;quot;]&amp;quot;, &amp;quot;}&amp;quot;, &amp;quot;,&amp;quot;)&lt;br /&gt;
local escape_chars  = create_set(&amp;quot;\\&amp;quot;, &amp;quot;/&amp;quot;, &#039;&amp;quot;&#039;, &amp;quot;b&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;r&amp;quot;, &amp;quot;t&amp;quot;, &amp;quot;u&amp;quot;)&lt;br /&gt;
local literals      = create_set(&amp;quot;true&amp;quot;, &amp;quot;false&amp;quot;, &amp;quot;null&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local literal_map = {&lt;br /&gt;
  [ &amp;quot;true&amp;quot;  ] = true,&lt;br /&gt;
  [ &amp;quot;false&amp;quot; ] = false,&lt;br /&gt;
  [ &amp;quot;null&amp;quot;  ] = nil,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function next_char(str, idx, set, negate)&lt;br /&gt;
  for i = idx, #str do&lt;br /&gt;
    if set[str:sub(i, i)] ~= negate then&lt;br /&gt;
      return i&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  return #str + 1&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function decode_error(str, idx, msg)&lt;br /&gt;
  local line_count = 1&lt;br /&gt;
  local col_count = 1&lt;br /&gt;
  for i = 1, idx - 1 do&lt;br /&gt;
    col_count = col_count + 1&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;\n&amp;quot; then&lt;br /&gt;
      line_count = line_count + 1&lt;br /&gt;
      col_count = 1&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;%s at line %d col %d&amp;quot;, msg, line_count, col_count) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function codepoint_to_utf8(n)&lt;br /&gt;
  -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&amp;amp;id=iws-appendixa&lt;br /&gt;
  local f = math.floor&lt;br /&gt;
  if n &amp;lt;= 0x7f then&lt;br /&gt;
    return string.char(n)&lt;br /&gt;
  elseif n &amp;lt;= 0x7ff then&lt;br /&gt;
    return string.char(f(n / 64) + 192, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0xffff then&lt;br /&gt;
    return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0x10ffff then&lt;br /&gt;
    return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,&lt;br /&gt;
                       f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;invalid unicode codepoint &#039;%x&#039;&amp;quot;, n) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_unicode_escape(s)&lt;br /&gt;
  local n1 = tonumber( s:sub(1, 4),  16 )&lt;br /&gt;
  local n2 = tonumber( s:sub(7, 10), 16 )&lt;br /&gt;
   -- Surrogate pair?&lt;br /&gt;
  if n2 then&lt;br /&gt;
    return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)&lt;br /&gt;
  else&lt;br /&gt;
    return codepoint_to_utf8(n1)&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_string(str, i)&lt;br /&gt;
  local res = &amp;quot;&amp;quot;&lt;br /&gt;
  local j = i + 1&lt;br /&gt;
  local k = j&lt;br /&gt;
&lt;br /&gt;
  while j &amp;lt;= #str do&lt;br /&gt;
    local x = str:byte(j)&lt;br /&gt;
&lt;br /&gt;
    if x &amp;lt; 32 then&lt;br /&gt;
      decode_error(str, j, &amp;quot;control character in string&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    elseif x == 92 then -- `\`: Escape&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      j = j + 1&lt;br /&gt;
      local c = str:sub(j, j)&lt;br /&gt;
      if c == &amp;quot;u&amp;quot; then&lt;br /&gt;
        local hex = str:match(&amp;quot;^[dD][89aAbB]%x%x\\u%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or str:match(&amp;quot;^%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or decode_error(str, j - 1, &amp;quot;invalid unicode escape in string&amp;quot;)&lt;br /&gt;
        res = res .. parse_unicode_escape(hex)&lt;br /&gt;
        j = j + #hex&lt;br /&gt;
      else&lt;br /&gt;
        if not escape_chars[c] then&lt;br /&gt;
          decode_error(str, j - 1, &amp;quot;invalid escape char &#039;&amp;quot; .. c .. &amp;quot;&#039; in string&amp;quot;)&lt;br /&gt;
        end&lt;br /&gt;
        res = res .. escape_char_map_inv[c]&lt;br /&gt;
      end&lt;br /&gt;
      k = j + 1&lt;br /&gt;
&lt;br /&gt;
    elseif x == 34 then -- `&amp;quot;`: End of string&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      return res, j + 1&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    j = j + 1&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  decode_error(str, i, &amp;quot;expected closing quote for string&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_number(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local s = str:sub(i, x - 1)&lt;br /&gt;
  local n = tonumber(s)&lt;br /&gt;
  if not n then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid number &#039;&amp;quot; .. s .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return n, x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_literal(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local word = str:sub(i, x - 1)&lt;br /&gt;
  if not literals[word] then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid literal &#039;&amp;quot; .. word .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return literal_map[word], x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_array(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  local n = 1&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local x&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of array?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;]&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read token&lt;br /&gt;
    x, i = parse(str, i)&lt;br /&gt;
    res[n] = x&lt;br /&gt;
    n = n + 1&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;]&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;]&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_object(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local key, val&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of object?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;}&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read key&lt;br /&gt;
    if str:sub(i, i) ~= &#039;&amp;quot;&#039; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected string for key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    key, i = parse(str, i)&lt;br /&gt;
    -- Read &#039;:&#039; delimiter&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    if str:sub(i, i) ~= &amp;quot;:&amp;quot; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected &#039;:&#039; after key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    i = next_char(str, i + 1, space_chars, true)&lt;br /&gt;
    -- Read value&lt;br /&gt;
    val, i = parse(str, i)&lt;br /&gt;
    -- Set&lt;br /&gt;
    res[key] = val&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;}&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;}&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local char_func_map = {&lt;br /&gt;
  [ &#039;&amp;quot;&#039; ] = parse_string,&lt;br /&gt;
  [ &amp;quot;0&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;1&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;2&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;3&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;4&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;5&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;6&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;7&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;8&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;9&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;-&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;t&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;f&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;n&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;[&amp;quot; ] = parse_array,&lt;br /&gt;
  [ &amp;quot;{&amp;quot; ] = parse_object,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
parse = function(str, idx)&lt;br /&gt;
  local chr = str:sub(idx, idx)&lt;br /&gt;
  local f = char_func_map[chr]&lt;br /&gt;
  if f then&lt;br /&gt;
    return f(str, idx)&lt;br /&gt;
  end&lt;br /&gt;
  decode_error(str, idx, &amp;quot;unexpected character &#039;&amp;quot; .. chr .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function json.decode(str)&lt;br /&gt;
  if type(str) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
    error(&amp;quot;expected argument of type string, got &amp;quot; .. type(str))&lt;br /&gt;
  end&lt;br /&gt;
  local res, idx = parse(str, next_char(str, 1, space_chars, true))&lt;br /&gt;
  idx = next_char(str, idx, space_chars, true)&lt;br /&gt;
  if idx &amp;lt;= #str then&lt;br /&gt;
    decode_error(str, idx, &amp;quot;trailing garbage&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Loop&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- A naive little event loop that lets you track running coroutines and dispatches&lt;br /&gt;
-- the events they&#039;re waiting for to them.&lt;br /&gt;
&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; awaited event&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    -- false has no semantic meaning here, we just want to reserve the space for&lt;br /&gt;
    -- the coroutine in the table without saying we&#039;re awaiting anything yet.&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- (internal) run the event&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    assert(coroutine.status(coro) ~= &amp;quot;dead&amp;quot;)&lt;br /&gt;
    -- assert(not self.running)&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    -- Since we&#039;re running, this isn&#039;t currently awaiting anything&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local success, ret = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    -- If we&#039;re not done then ret should be the kind of event we&#039;re awaiting.&lt;br /&gt;
    -- Otherwise it&#039;s an error message or something. Who cares.&lt;br /&gt;
    if not success then&lt;br /&gt;
        -- Get rid of the coro&lt;br /&gt;
        if ret then&lt;br /&gt;
            ll.OwnerSay(`Might have an error: {ret}`)&lt;br /&gt;
        end&lt;br /&gt;
        ret = nil&lt;br /&gt;
    end&lt;br /&gt;
    self._coros[coro] = ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Wake up any tasks that are waiting for this kind of event&lt;br /&gt;
function EventLoop:handle_event(name, ...)&lt;br /&gt;
    -- We may mutate self._coros, so do a clone&lt;br /&gt;
    local coros = table.clone(self._coros)&lt;br /&gt;
    ll.OwnerSay(`Handling {name}`)&lt;br /&gt;
&lt;br /&gt;
    for coro, awaited_event in coros do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            -- Hmm, this coroutine is dead, prune it.&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if awaited_event == name then&lt;br /&gt;
            ll.OwnerSay(`Dispatching {name} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_event_catcher(name)&lt;br /&gt;
    local function catcher(...)&lt;br /&gt;
        ll.OwnerSay(&amp;quot;yay&amp;quot;)&lt;br /&gt;
        EventLoop:handle_event(name, ...)&lt;br /&gt;
    end&lt;br /&gt;
    return catcher&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Helper function for waiting and telling the EventLoop what event you want&lt;br /&gt;
local function await_event(kind)&lt;br /&gt;
    -- You had better do this inside a running event loop&lt;br /&gt;
    assert(EventLoop.running)&lt;br /&gt;
    -- This will yield, the EventLoop will resume us once it has&lt;br /&gt;
    -- an event we might be interested in.&lt;br /&gt;
    return coroutine.yield(kind)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Script-specific functions&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
local function hide_children()&lt;br /&gt;
    for i=2,ll.GetNumberOfPrims() do&lt;br /&gt;
        -- Hide all the child prims&lt;br /&gt;
        ll.SetLinkAlpha(i, 0, -1)&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(i, {&lt;br /&gt;
            PRIM_SIZE, vector(0.5, 0.5, 0.5),&lt;br /&gt;
            PRIM_TEXT, &amp;quot;&amp;quot;, vector(1,1,1), 1&lt;br /&gt;
        })&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_textbox(avatar_id, message, channel)&lt;br /&gt;
    ll.TextBox(avatar_id, message, channel)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        local resp_channel, name, id, resp = await_event(&amp;quot;listen&amp;quot;)&lt;br /&gt;
        if id ~= avatar_id or channel ~= resp_channel then&lt;br /&gt;
            -- If this isn&#039;t a message we&#039;re interested in then wait for the next event&lt;br /&gt;
            ll.OwnerSay(`{id}, {avatar_id}, {resp_channel}, {channel}`)&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return resp&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function make_json_request(url)&lt;br /&gt;
    local req_id = ll.HTTPRequest(url, {HTTP_BODY_MAXLENGTH, integer(16000)}, &amp;quot;&amp;quot;)&lt;br /&gt;
    while true do&lt;br /&gt;
        local ev_id, status, metadata, body = await_event(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
        if ev_id ~= req_id then&lt;br /&gt;
            -- Not a response for the HTTP request we were waiting on, wait for the next event.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return status, metadata, body&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function request_weather()&lt;br /&gt;
    -- And you can use a wrapper like this to make an async function callable within both normal and coroutine contexts.&lt;br /&gt;
    -- Normally this sort of thing would not be necessary, but here it is since the EventLoop lives in our own code,&lt;br /&gt;
    -- and isn&#039;t a property of the actual script engine.&lt;br /&gt;
    if gWeatherRequestTask then&lt;br /&gt;
        EventLoop:kill_task(gWeatherRequestTask)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    gWeatherRequestTask = EventLoop:create_task(function()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Requesting weather&amp;quot;)&lt;br /&gt;
        local status, metadata, body = make_json_request(`https://api.open-meteo.com/v1/forecast?latitude={gLatLong[1]}&amp;amp;longitude={gLatLong[2]}&amp;amp;current=temperature_2m&amp;amp;daily=temperature_2m_max`)&lt;br /&gt;
        if status ~= 200 then&lt;br /&gt;
            ll.OwnerSay(&amp;quot;Error requesting weather&amp;quot;)&lt;br /&gt;
            return&lt;br /&gt;
        end&lt;br /&gt;
        local parsed = json.decode(body)&lt;br /&gt;
&lt;br /&gt;
        local current = parsed[&amp;quot;current&amp;quot;]&lt;br /&gt;
        ll.OwnerSay(`Temperature is {current[&amp;quot;temperature_2m&amp;quot;]}C`)&lt;br /&gt;
        ll.SetText(`Temperature in {gCityName} is currently {current[&amp;quot;temperature_2m&amp;quot;]}C`, vector(1,1,1), 1)&lt;br /&gt;
&lt;br /&gt;
        -- Show the highs for each day&lt;br /&gt;
        local days = parsed[&amp;quot;daily&amp;quot;][&amp;quot;temperature_2m_max&amp;quot;]&lt;br /&gt;
        for i=1,#days do&lt;br /&gt;
            local link_num = i + 1&lt;br /&gt;
            -- Higher temps = bigger on Z, negatives are unrepresentable :)&lt;br /&gt;
            local z_size = (days[i] / 30) * 2&lt;br /&gt;
            ll.SetLinkAlpha(link_num, 1, -1)&lt;br /&gt;
            ll.SetLinkPrimitiveParamsFast(link_num, {&lt;br /&gt;
                PRIM_SIZE, vector(0.5, 0.5, z_size),&lt;br /&gt;
                PRIM_TEXT, `{days[i]}C`, vector(1,1,1), 1&lt;br /&gt;
            })&lt;br /&gt;
        end&lt;br /&gt;
    end)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ask_for_city()&lt;br /&gt;
    local resp = send_textbox(ll.GetOwner(), &amp;quot;What city do you want the weather for?&amp;quot;, TEXTBOX_CHANNEL)&lt;br /&gt;
    if not resp then&lt;br /&gt;
        ll.OwnerSay(&amp;quot;Got no response for city request.&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    ll.OwnerSay(`You want the weather for {resp}.`)&lt;br /&gt;
    gLatLong = nil&lt;br /&gt;
    gCityName = resp&lt;br /&gt;
&lt;br /&gt;
    hide_children()&lt;br /&gt;
&lt;br /&gt;
    -- request the lat and long for this city&lt;br /&gt;
    local status, metadata, body = make_json_request(`https://nominatim.openstreetmap.org/search.php?city={ll.EscapeURL(resp)}&amp;amp;format=jsonv2`)&lt;br /&gt;
    local parsed = json.decode(body)&lt;br /&gt;
    if not #parsed then&lt;br /&gt;
        ll.OwnerSay(`Got no results for {resp}!`)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local first_result = parsed[1]&lt;br /&gt;
    gLatLong = {first_result[&amp;quot;lat&amp;quot;], first_result[&amp;quot;lon&amp;quot;]}&lt;br /&gt;
    ll.OwnerSay(`City Lat: {gLatLong[1]}, Long: {gLatLong[2]}`)&lt;br /&gt;
&lt;br /&gt;
    request_weather()&lt;br /&gt;
&lt;br /&gt;
    -- request weather again in 3 mins&lt;br /&gt;
    ll.SetTimerEvent(180)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Handlers&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- If we don&#039;t care about conventionally dispatched events, we can just set the event handler to directly send&lt;br /&gt;
-- event data to the event loop.&lt;br /&gt;
http_response = EventLoop:create_event_catcher(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
listen = EventLoop:create_event_catcher(&amp;quot;listen&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- More conventional event handlers work too.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    for i=0,num-1 do&lt;br /&gt;
        if ll.GetOwner() ~= ll.DetectedKey(i) then&lt;br /&gt;
            -- Go to next loop if it wasn&#039;t the owner touching us.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if gCityRequestTask then&lt;br /&gt;
            EventLoop:kill_task(gCityRequestTask)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Spin up a task to ask the user for their city&lt;br /&gt;
        gCityRequestTask = EventLoop:create_task(ask_for_city)&lt;br /&gt;
        break&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function timer()&lt;br /&gt;
    request_weather()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Not strictly necessary, but I like having the main function have its own scope.&lt;br /&gt;
-- Rather than just have top-level logic.&lt;br /&gt;
local function main()&lt;br /&gt;
    ll.Listen(TEXTBOX_CHANNEL, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.SetText(&amp;quot;&amp;quot;, vector(1,1,1), 1)&lt;br /&gt;
    hide_children()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Weatherbox operational!&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
main()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218005</id>
		<title>Lua Alpha</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_Alpha&amp;diff=1218005"/>
		<updated>2025-03-04T22:37:22Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:Luau.png|720px|thumb|right|Luau logo]]&lt;br /&gt;
&lt;br /&gt;
= Second Life Luau Alpha =&lt;br /&gt;
&lt;br /&gt;
We&#039;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.&lt;br /&gt;
&lt;br /&gt;
== What is Luau? ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Why Luau? ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== How to Get Started with Luau in Second Life ==&lt;br /&gt;
&lt;br /&gt;
In order to play with Luau, you&#039;ll need to download our Lua project viewer, and log onto our Aditi beta grid.&lt;br /&gt;
&lt;br /&gt;
* Access the latest build of the Luau-enabled Second Life Viewer from [https://releasenotes.secondlife.com/viewer/7.1.12.13526902562.html here].&lt;br /&gt;
&lt;br /&gt;
Once you&#039;ve got the new viewer and have logged onto the beta grid, head over to these Luau-enabled regions:&lt;br /&gt;
* Luau Yardang&lt;br /&gt;
* Luau Tombolo&lt;br /&gt;
* Luau Mesa&lt;br /&gt;
* Luau Tideland&lt;br /&gt;
&lt;br /&gt;
When editing a script in the new Lua project viewer, you&#039;ll notice a new &#039;&#039;&#039;Compiler&#039;&#039;&#039; 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).&lt;br /&gt;
&lt;br /&gt;
[[File:Compiler_dropdown.png|Compiler selection dropdown]]&lt;br /&gt;
&lt;br /&gt;
Compiler drop-down options:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;LSL: Legacy (LSO2)&#039;&#039;&#039; - Scripts written in LSL, to be run on the old LSO2 VM&lt;br /&gt;
* &#039;&#039;&#039;LSL: Mono&#039;&#039;&#039;- Scripts written in LSL, to be run on the Mono VM&lt;br /&gt;
* &#039;&#039;&#039;Lua&#039;&#039;&#039; - Scripts written in Lua, to be run on the Luau VM&lt;br /&gt;
* &#039;&#039;&#039;LSL/Luau&#039;&#039;&#039;- Scripts written in LSL, to be run on the Luau VM&lt;br /&gt;
&lt;br /&gt;
=== Transitioning from LSL to Luau ===&lt;br /&gt;
* &#039;&#039;&#039;Function Namespacing:&#039;&#039;&#039;&lt;br /&gt;
** In Luau, Linden Lab functions have been moved under the &#039;&#039;&#039;ll&#039;&#039;&#039; namespace.&lt;br /&gt;
** For example:&lt;br /&gt;
*** &#039;&#039;llSay&#039;&#039; becomes &#039;&#039;ll.Say&#039;&#039;&lt;br /&gt;
*** &#039;&#039;llGetPos&#039;&#039; becomes &#039;&#039;ll.GetPos&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;Constants:&#039;&#039;&#039;&lt;br /&gt;
** Constants like &#039;&#039;PERMISSION_TAKE_CONTROLS&#039;&#039; and &#039;&#039;PRIM_ROT_LOCAL&#039;&#039; are not predefined in Luau.&lt;br /&gt;
** You must specify these constants if you wish to reference them in your scripts.&lt;br /&gt;
&lt;br /&gt;
=== Luau Libraries ===&lt;br /&gt;
* &#039;&#039;&#039;Coroutines:&#039;&#039;&#039;&lt;br /&gt;
** Luau supports coroutines, allowing for cooperative multitasking within scripts.&lt;br /&gt;
** Key functions include:&lt;br /&gt;
*** &#039;&#039;coroutine.create&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.status&#039;&#039;&lt;br /&gt;
*** &#039;&#039;coroutine.resume&#039;&#039;&lt;br /&gt;
** Refer to the [https://luau.org/library#coroutine-library coroutine library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Bitwise Operations:&#039;&#039;&#039;&lt;br /&gt;
** Luau includes a &#039;&#039;bit32&#039;&#039; library for bitwise operations, enabling more efficient data manipulation.&lt;br /&gt;
** Refer to the [https://luau.org/library#bit32-library bit32 library documentation] for more details.&lt;br /&gt;
* &#039;&#039;&#039;Standard Library:&#039;&#039;&#039;&lt;br /&gt;
** Luau comes equipped with a standard library of functions designed to manipulate built-in data types.&lt;br /&gt;
** Explore the [https://luau.org/library Luau Standard Library] for a comprehensive list of available functions.&lt;br /&gt;
&lt;br /&gt;
== Feedback and Support ==&lt;br /&gt;
&lt;br /&gt;
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].&lt;br /&gt;
&lt;br /&gt;
== Example Scripts ==&lt;br /&gt;
&lt;br /&gt;
To help you get started, we&#039;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&#039;s desire!&lt;br /&gt;
&lt;br /&gt;
=== dialog.lua ===&lt;br /&gt;
This script demonstrates how one can interact with dialog menus.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Define the menu buttons and dialog message.&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local ToucherID = nil&lt;br /&gt;
local dialogChannel = nil&lt;br /&gt;
local listenHandle = nil&lt;br /&gt;
&lt;br /&gt;
-- This function is called when the script first starts.&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Get the object&#039;s key and compute a dialog channel number.&lt;br /&gt;
    local key = ll.GetKey()&lt;br /&gt;
    -- Extract the last 7 characters of the key and convert it from hex.&lt;br /&gt;
    dialogChannel = -1 - tonumber(string.sub(tostring(key), -7, -1), 16)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched.&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    ToucherID = ll.DetectedKey(0)&lt;br /&gt;
    -- If there is already a listen handle, then remove it&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
    end&lt;br /&gt;
    listenHandle = ll.Listen(dialogChannel, &amp;quot;&amp;quot;, ToucherID, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
    -- Set a 60-second timer for response.&lt;br /&gt;
    ll.SetTimerEvent(60.0)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when a dialog response is received.&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    if message == &amp;quot;-&amp;quot; then&lt;br /&gt;
        -- Redisplay the dialog if the &amp;quot;-&amp;quot; option is selected.&lt;br /&gt;
        ll.Dialog(ToucherID, dialogInfo, buttons, dialogChannel)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Stop the timer, and stop the listening handler.&lt;br /&gt;
    ll.ListenRemove(listenHandle)&lt;br /&gt;
    ll.SetTimerEvent(0)&lt;br /&gt;
    -- Let the user know what they selected&lt;br /&gt;
    ll.Say(0, `You selected {message}`)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the timer expires.&lt;br /&gt;
function timer()&lt;br /&gt;
    -- Stop the timer and clean up the listener.&lt;br /&gt;
    if listenHandle then&lt;br /&gt;
        ll.SetTimerEvent(0)&lt;br /&gt;
        ll.ListenRemove(listenHandle)&lt;br /&gt;
        ll.Whisper(0, &amp;quot;Sorry. You snooze; you lose.&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Invoke state_entry on startup, since simulator doesn&#039;t invoke &lt;br /&gt;
-- it like it does in LSL&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dialog_coroutine.lua ===&lt;br /&gt;
This script demonstrates how one could use coroutines to handle dialog responses, with multi-user support.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Minimal EventLoop&lt;br /&gt;
-------------------------&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; eventName it’s waiting for&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status(coro) ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Internal helper: resumes a coroutine&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local ok, eventAwaited = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    if not ok then&lt;br /&gt;
        ll.OwnerSay(`Coroutine error: {eventAwaited}`)&lt;br /&gt;
        self._coros[coro] = nil&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- If still alive, &#039;eventAwaited&#039; is the next event it wants&lt;br /&gt;
    self._coros[coro] = eventAwaited&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:handle_event(eventName, ...)&lt;br /&gt;
    ll.OwnerSay(`Handling event {eventName}`)&lt;br /&gt;
    local snapshot = table.clone(self._coros)&lt;br /&gt;
    for coro, waitingFor in pairs(snapshot) do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        elseif waitingFor == eventName then&lt;br /&gt;
            ll.OwnerSay(`Dispatching event {eventName} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Coroutines use this to yield until an event&lt;br /&gt;
local function await_event(name)&lt;br /&gt;
    assert(EventLoop.running, &amp;quot;await_event called outside a coroutine!&amp;quot;)&lt;br /&gt;
    return coroutine.yield(name)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------&lt;br /&gt;
-- Script Logic&lt;br /&gt;
-------------------------&lt;br /&gt;
local buttons = {&amp;quot;-&amp;quot;, &amp;quot;Red&amp;quot;, &amp;quot;Green&amp;quot;, &amp;quot;Yellow&amp;quot;}&lt;br /&gt;
local dialogInfo = &amp;quot;\nPlease make a choice.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- Use the chat listener to feed the event-loop&lt;br /&gt;
function listen(channel, name, sender_id, message)&lt;br /&gt;
    -- We handle all &#039;listen&#039; events via the event-loop&lt;br /&gt;
    EventLoop:handle_event(`listen_{channel}`, channel, name, sender_id, message)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the script starts&lt;br /&gt;
function state_entry()&lt;br /&gt;
    -- Seed math.random so each new script run doesn’t repeat the same channels&lt;br /&gt;
    math.randomseed(ll.GetUnixTime())&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Script started with random channels for each user.&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- A coroutine function for a single user&#039;s dialog flow&lt;br /&gt;
local function handle_dialog_for_user(userId)&lt;br /&gt;
    -- Use a random channel&lt;br /&gt;
    local channel = math.random(0x1, 0xFFFF)&lt;br /&gt;
    -- Create a listener for that channel&lt;br /&gt;
    local listenHandle = ll.Listen(channel, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        -- Show the user a dialog&lt;br /&gt;
        ll.Dialog(userId, dialogInfo, buttons, channel)&lt;br /&gt;
&lt;br /&gt;
        -- Wait for the next &#039;listen&#039; event (channel, name, sender_id, message)&lt;br /&gt;
        local c, n, sid, msg = await_event(`listen_{channel}`)&lt;br /&gt;
&lt;br /&gt;
        -- If this &amp;quot;listen&amp;quot; event isn&#039;t for our channel/user, ignore it&lt;br /&gt;
        if c == channel and sid == userId then&lt;br /&gt;
            -- If user pressed &amp;quot;-&amp;quot;, re-display the menu, else they picked a final color&lt;br /&gt;
            if msg ~= &amp;quot;-&amp;quot; then&lt;br /&gt;
                ll.Say(0, `{n} selected {msg}`)&lt;br /&gt;
                -- Now that they&#039;ve chosen something else, remove the listener and finish&lt;br /&gt;
                ll.ListenRemove(listenHandle)&lt;br /&gt;
                return&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched&lt;br /&gt;
function touch_start(num_detected)&lt;br /&gt;
    for i=0, num_detected-1 do&lt;br /&gt;
        local toucherId = ll.DetectedKey(i)&lt;br /&gt;
        -- Create a separate coroutine for each person who touches&lt;br /&gt;
        EventLoop:create_task(function()&lt;br /&gt;
            handle_dialog_for_user(toucherId)&lt;br /&gt;
        end)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Run state_entry on load:&lt;br /&gt;
state_entry()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== vehicle.lua ===&lt;br /&gt;
This script demonstrates how one may create their own vehicle scripts, which don’t rely on the underlying LL vehicle system. &lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Control and permission constants&lt;br /&gt;
local PERMISSION_TAKE_CONTROLS = 0x4&lt;br /&gt;
local CONTROL_FWD       = 0x1&lt;br /&gt;
local CONTROL_BACK      = 0x2&lt;br /&gt;
local CONTROL_UP        = 0x10&lt;br /&gt;
local CONTROL_DOWN      = 0x20&lt;br /&gt;
local CONTROL_LEFT      = 0x4&lt;br /&gt;
local CONTROL_RIGHT     = 0x8&lt;br /&gt;
local PRIM_ROT_LOCAL    = integer(29)&lt;br /&gt;
&lt;br /&gt;
-- These two will hold the link numbers for left and right front wheels&lt;br /&gt;
local LINK_WHEEL_LEFT = 0&lt;br /&gt;
local LINK_WHEEL_RIGHT = 0&lt;br /&gt;
&lt;br /&gt;
-- Integra Type R Engine properties&lt;br /&gt;
local Engine = {&lt;br /&gt;
    rpm = 800,              -- current engine RPM (starts at idle)&lt;br /&gt;
    gear = 0,               -- 0 = neutral; -1 = reverse; 1-5 for forward gears&lt;br /&gt;
    idleRPM = 800,          -- idle RPM&lt;br /&gt;
    maxRPM = 8300,          -- redline&lt;br /&gt;
    throttle = 0.0,         -- throttle value (0.0 to 1.0)&lt;br /&gt;
    gearRatios = {          -- Integra Type R gear ratios&lt;br /&gt;
        [-1] = 3.727,       -- reverse gear ratio&lt;br /&gt;
        [0]  = 0,          -- neutral&lt;br /&gt;
        [1]  = 3.727,&lt;br /&gt;
        [2]  = 2.256,&lt;br /&gt;
        [3]  = 1.729,&lt;br /&gt;
        [4]  = 1.416,&lt;br /&gt;
        [5]  = 1.194&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local FINAL_DRIVE = 4.1   -- Final drive ratio&lt;br /&gt;
local WHEEL_RADIUS = 0.3  -- in meters&lt;br /&gt;
&lt;br /&gt;
-- Vehicle properties (using realistic Integra Type R mass)&lt;br /&gt;
local Vehicle = {&lt;br /&gt;
    speed = 0,      -- speed in m/s; negative indicates reverse motion&lt;br /&gt;
    mass = 1200,    -- mass in kg&lt;br /&gt;
    angle = 0,      -- heading angle in radians&lt;br /&gt;
    steering = 0,   -- steering input: -1 (left), 0 (none), 1 (right)&lt;br /&gt;
    brake = false   -- flag indicating if brakes are applied&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Engine constants (using realistic Integra Type R values)&lt;br /&gt;
local MAX_TORQUE = 200   -- in Nm&lt;br /&gt;
&lt;br /&gt;
-- Brake force constant (for active braking)&lt;br /&gt;
local BRAKE_FORCE = 4000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Define a torque curve function:&lt;br /&gt;
local function torqueCurve(rpm)&lt;br /&gt;
    local peakRPM = 6000&lt;br /&gt;
    if rpm &amp;lt; peakRPM then&lt;br /&gt;
        return 0.8 + 0.2 * ((rpm - Engine.idleRPM) / (peakRPM - Engine.idleRPM))&lt;br /&gt;
    else&lt;br /&gt;
        local factor = 1 - 0.5 * ((rpm - peakRPM) / (Engine.maxRPM - peakRPM))&lt;br /&gt;
        return math.max(0.5, factor)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Engine braking force when throttle is zero in gear.&lt;br /&gt;
local ENGINE_BRAKE_FORCE = 2000  -- in Newtons&lt;br /&gt;
&lt;br /&gt;
-- Friction and drag constants&lt;br /&gt;
local G = 9.81            -- gravity (m/s^2)&lt;br /&gt;
local C_rr_inGear = 0.015 -- rolling resistance coefficient in gear&lt;br /&gt;
local C_rr_neutral = 0.05 -- rolling resistance coefficient in neutral&lt;br /&gt;
&lt;br /&gt;
local airDensity = 1.225      -- kg/m^3&lt;br /&gt;
local dragCoefficient = 0.3   -- aerodynamic drag coefficient&lt;br /&gt;
local frontalArea = 2.2       -- in m^2&lt;br /&gt;
&lt;br /&gt;
local function getAerodynamicDrag(speed)&lt;br /&gt;
    return 0.5 * airDensity * dragCoefficient * frontalArea * speed * speed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Compute engine RPM from vehicle speed when in gear.&lt;br /&gt;
local function computeEngineRPMFromSpeed()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return Engine.rpm  -- In neutral, RPM is managed independently.&lt;br /&gt;
    else&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local rpm = math.abs(Vehicle.speed) * conversionFactor&lt;br /&gt;
        if rpm &amp;lt; Engine.idleRPM then rpm = Engine.idleRPM end&lt;br /&gt;
        return rpm&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- For neutral, update RPM toward a target based on throttle.&lt;br /&gt;
local function updateEngineRPMNeutral()&lt;br /&gt;
    local targetRPM = Engine.idleRPM + (Engine.maxRPM - Engine.idleRPM) * Engine.throttle&lt;br /&gt;
    local changeRate = 200  -- RPM per tick&lt;br /&gt;
    if Engine.rpm &amp;lt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.min(Engine.rpm + changeRate, targetRPM)&lt;br /&gt;
    elseif Engine.rpm &amp;gt; targetRPM then&lt;br /&gt;
        Engine.rpm = math.max(Engine.rpm - changeRate, targetRPM)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Calculate the engine force delivered to the wheels.&lt;br /&gt;
local function getEngineForce()&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        return 0  -- In neutral, no drive force is transmitted.&lt;br /&gt;
    else&lt;br /&gt;
        if Engine.throttle == 0 then&lt;br /&gt;
            -- Apply engine braking when throttle is released.&lt;br /&gt;
            return -ENGINE_BRAKE_FORCE&lt;br /&gt;
        else&lt;br /&gt;
            local torque = Engine.throttle * MAX_TORQUE * torqueCurve(Engine.rpm)&lt;br /&gt;
            local force = (torque * Engine.gearRatios[Engine.gear] * FINAL_DRIVE) / WHEEL_RADIUS&lt;br /&gt;
            return force&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Starts the engine by setting it to idle RPM.&lt;br /&gt;
local function startEngine()&lt;br /&gt;
    Engine.rpm = Engine.idleRPM&lt;br /&gt;
    ll.Say(0, &amp;quot;Engine started. RPM: &amp;quot; .. Engine.rpm)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Shifts the gear if valid.&lt;br /&gt;
local function shiftGear(newGear)&lt;br /&gt;
    if Engine.gearRatios[newGear] then&lt;br /&gt;
        Engine.gear = newGear&lt;br /&gt;
        ll.Say(0, &amp;quot;Shifted to gear &amp;quot; .. newGear)&lt;br /&gt;
    else&lt;br /&gt;
        ll.Say(0, &amp;quot;Invalid gear: &amp;quot; .. newGear)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function findWheels()&lt;br /&gt;
    local total = ll.GetNumberOfPrims()&lt;br /&gt;
    for i = 1, total do&lt;br /&gt;
        local name = ll.GetLinkName(i)&lt;br /&gt;
        if name == &amp;quot;WHEEL_LF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_LEFT = i&lt;br /&gt;
        elseif name == &amp;quot;WHEEL_RF&amp;quot; then&lt;br /&gt;
            LINK_WHEEL_RIGHT = i&lt;br /&gt;
        end&lt;br /&gt;
    end &lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local castRayCounter = 0&lt;br /&gt;
local lastGrounded = false&lt;br /&gt;
&lt;br /&gt;
local function isVehicleOnGround()&lt;br /&gt;
    castRayCounter = castRayCounter + 1&lt;br /&gt;
    if castRayCounter &amp;gt;= 5 then&lt;br /&gt;
        castRayCounter = 0&lt;br /&gt;
        local pos = ll.GetPos()&lt;br /&gt;
        local rayDistance = 4.5  -- threshold distance (in meters) to consider as &amp;quot;grounded&amp;quot;&lt;br /&gt;
        local rayStart = pos&lt;br /&gt;
        local rayEnd = pos + vector(0, 0, -rayDistance)&lt;br /&gt;
        local hitResult = ll.CastRay(rayStart, rayEnd, {})&lt;br /&gt;
        if table.getn(hitResult) == 3 then&lt;br /&gt;
            lastGrounded = true&lt;br /&gt;
        else&lt;br /&gt;
            lastGrounded = false&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return lastGrounded&lt;br /&gt;
end &lt;br /&gt;
&lt;br /&gt;
-- Timer event: updates engine, vehicle physics, steering, and rotates the wheels based on steering.&lt;br /&gt;
function timer()&lt;br /&gt;
    local dt = 0.1  -- time step in seconds&lt;br /&gt;
&lt;br /&gt;
    -- For neutral, update RPM based on throttle.&lt;br /&gt;
    if Engine.gear == 0 then&lt;br /&gt;
        updateEngineRPMNeutral()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Calculate forces.&lt;br /&gt;
    local C_rr = (Engine.gear == 0) and C_rr_neutral or C_rr_inGear&lt;br /&gt;
    local F_roll = Vehicle.mass * G * C_rr&lt;br /&gt;
    local engineForce = getEngineForce()&lt;br /&gt;
    local dragForce = getAerodynamicDrag(math.abs(Vehicle.speed))&lt;br /&gt;
    local netForce = engineForce - (F_roll + dragForce)&lt;br /&gt;
&lt;br /&gt;
    -- Apply braking force if brakes are engaged.&lt;br /&gt;
    if Vehicle.brake then&lt;br /&gt;
        netForce = netForce - BRAKE_FORCE&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local acceleration = netForce / Vehicle.mass&lt;br /&gt;
    local vel = ll.GetVel()&lt;br /&gt;
    local horizontal_vec = vector(vel.x, vel.y, 0) &lt;br /&gt;
    Vehicle.speed = (ll.VecMag(horizontal_vec) + acceleration * dt)&lt;br /&gt;
&lt;br /&gt;
    -- Limit vehicle speed based on gear.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        if Vehicle.speed &amp;lt; 0 then &lt;br /&gt;
            Vehicle.speed = 0 &lt;br /&gt;
        end&lt;br /&gt;
        local conversionFactor = Engine.gearRatios[Engine.gear] * FINAL_DRIVE * (60 / (2 * math.pi * WHEEL_RADIUS))&lt;br /&gt;
        local maxSpeed = Engine.maxRPM / conversionFactor&lt;br /&gt;
        if Vehicle.speed &amp;gt; maxSpeed then&lt;br /&gt;
            Vehicle.speed = maxSpeed&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- When in gear, update RPM based on the new speed.&lt;br /&gt;
    if Engine.gear ~= 0 then&lt;br /&gt;
        Engine.rpm = computeEngineRPMFromSpeed()&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Steering update using angular velocity and quaternions:&lt;br /&gt;
    local STEERING_RATE = math.rad(6)  -- desired steering rate per tick (radians)&lt;br /&gt;
    local yawDelta = Vehicle.steering * STEERING_RATE  -- small rotation for this tick&lt;br /&gt;
&lt;br /&gt;
    if yawDelta ~= 0 then&lt;br /&gt;
        local deltaQuat = ll.Euler2Rot(vector(0, 0, yawDelta))&lt;br /&gt;
        local angularSpeed = yawDelta / dt&lt;br /&gt;
        local angularVelocity = vector(0, 0, angularSpeed)&lt;br /&gt;
        ll.SetAngularVelocity(angularVelocity, 1)&lt;br /&gt;
        Vehicle.angle = Vehicle.angle + yawDelta&lt;br /&gt;
    else&lt;br /&gt;
        ll.SetAngularVelocity(vector(0, 0, 0), 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Display engine RPM and speed (converted to mph).&lt;br /&gt;
    local speed_mph = math.abs(Vehicle.speed) * 2.23694&lt;br /&gt;
    ll.SetText(&amp;quot;RPM: &amp;quot; .. math.floor(Engine.rpm) ..&lt;br /&gt;
               &amp;quot; | Speed: &amp;quot; .. string.format(&amp;quot;%.1f&amp;quot;, speed_mph) ..&lt;br /&gt;
               &amp;quot; mph&amp;quot;, vector(0,0,0), 1)&lt;br /&gt;
&lt;br /&gt;
    -- Only update velocity if the vehicle is near the ground, and we&#039;re in gear.&lt;br /&gt;
    if isVehicleOnGround() and Engine.gear ~= 0 then&lt;br /&gt;
        local velocityVector&lt;br /&gt;
        if Engine.gear &amp;gt; 0 then&lt;br /&gt;
            velocityVector = vector(Vehicle.speed, 0, 0)&lt;br /&gt;
        else&lt;br /&gt;
            -- Reverse gear&lt;br /&gt;
            velocityVector = vector(-Vehicle.speed, 0, 0)&lt;br /&gt;
        end&lt;br /&gt;
        ll.SetVelocity(velocityVector, 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Update the wheel rotations to visually match the steering.&lt;br /&gt;
    local maxWheelTurn = math.rad(30)  -- maximum wheel turn angle in radians&lt;br /&gt;
    local wheelAngle = -Vehicle.steering * maxWheelTurn&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_LEFT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_LEFT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(-4.71239, -wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if LINK_WHEEL_RIGHT &amp;gt; 0 then&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(&lt;br /&gt;
            LINK_WHEEL_RIGHT,&lt;br /&gt;
            {&lt;br /&gt;
                PRIM_ROT_LOCAL,&lt;br /&gt;
                ll.Euler2Rot(vector(4.71239, wheelAngle, 0))&lt;br /&gt;
            }&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when the object is touched: starts engine and simulation.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    ll.Say(0, &amp;quot;Touch detected, requesting controls and starting engine.&amp;quot;)&lt;br /&gt;
    ll.RequestPermissions(ll.GetOwner(), PERMISSION_TAKE_CONTROLS)&lt;br /&gt;
    startEngine()&lt;br /&gt;
    findWheels()&lt;br /&gt;
    ll.SetTimerEvent(0.1)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Called when runtime permissions are granted.&lt;br /&gt;
function run_time_permissions(perm)&lt;br /&gt;
    if bit32.band(perm, PERMISSION_TAKE_CONTROLS) then&lt;br /&gt;
        ll.Say(0, &amp;quot;Permissions granted.&amp;quot;)&lt;br /&gt;
        local controlMask = bit32.bor(CONTROL_FWD, CONTROL_BACK, CONTROL_UP, CONTROL_DOWN, CONTROL_LEFT, CONTROL_RIGHT)&lt;br /&gt;
        ll.TakeControls(controlMask, 1, 1)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Handle control events:&lt;br /&gt;
-- • CONTROL_FWD applies throttle.&lt;br /&gt;
-- • CONTROL_BACK applies the brakes.&lt;br /&gt;
-- • CONTROL_UP and CONTROL_DOWN shift gears.&lt;br /&gt;
-- • CONTROL_LEFT and CONTROL_RIGHT steer the vehicle.&lt;br /&gt;
function control(avatar_id, level, edge)&lt;br /&gt;
    local start = bit32.band(level, edge)            &lt;br /&gt;
    local finish = bit32.band(bit32.bnot(level), edge) &lt;br /&gt;
    local held = bit32.band(level, bit32.bnot(edge))   &lt;br /&gt;
    local untouched = bit32.bnot(bit32.bor(level, edge)) &lt;br /&gt;
&lt;br /&gt;
    if bit32.band(held, CONTROL_BACK) ~= 0 then&lt;br /&gt;
        Vehicle.brake = true&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    elseif bit32.band(held, CONTROL_FWD) ~= 0 then&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 1.0&lt;br /&gt;
        if Engine.gear == 0 then&lt;br /&gt;
            updateEngineRPMNeutral()&lt;br /&gt;
        end&lt;br /&gt;
    else&lt;br /&gt;
        Vehicle.brake = false&lt;br /&gt;
        Engine.throttle = 0.0&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_UP) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear + 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if bit32.band(start, CONTROL_DOWN) ~= 0 then&lt;br /&gt;
        shiftGear(Engine.gear - 1)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local steeringInput = 0&lt;br /&gt;
    if bit32.band(held, CONTROL_LEFT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput + 1&lt;br /&gt;
    end&lt;br /&gt;
    if bit32.band(held, CONTROL_RIGHT) ~= 0 then&lt;br /&gt;
        steeringInput = steeringInput - 1&lt;br /&gt;
    end&lt;br /&gt;
    Vehicle.steering = steeringInput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== weather_box.lua ===&lt;br /&gt;
This script demonstrates coroutines, as well as working with a JSON-based web API:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local TEXTBOX_CHANNEL = 323242&lt;br /&gt;
-- kludge because we don&#039;t have LSL constants in here yet&lt;br /&gt;
local HTTP_BODY_MAXLENGTH = integer(2)&lt;br /&gt;
local PRIM_TEXT = integer(26)&lt;br /&gt;
local PRIM_SIZE = integer(7)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
gCityName = &amp;quot;&amp;quot;&lt;br /&gt;
gLatLong = nil  -- A table of 2 elems when it&#039;s populated&lt;br /&gt;
gCityRequestTask = nil&lt;br /&gt;
gWeatherRequestTask = nil&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- JSON stuff&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- From https://github.com/rxi/json.lua/blob/master/json.lua&lt;br /&gt;
&lt;br /&gt;
local json = {}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map = {&lt;br /&gt;
  [ &amp;quot;\\&amp;quot; ] = &amp;quot;\\&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\&amp;quot;&amp;quot; ] = &amp;quot;\&amp;quot;&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\b&amp;quot; ] = &amp;quot;b&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\f&amp;quot; ] = &amp;quot;f&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\n&amp;quot; ] = &amp;quot;n&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\r&amp;quot; ] = &amp;quot;r&amp;quot;,&lt;br /&gt;
  [ &amp;quot;\t&amp;quot; ] = &amp;quot;t&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local escape_char_map_inv = { [ &amp;quot;/&amp;quot; ] = &amp;quot;/&amp;quot; }&lt;br /&gt;
for k, v in pairs(escape_char_map) do&lt;br /&gt;
  escape_char_map_inv[v] = k&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local parse&lt;br /&gt;
&lt;br /&gt;
local function create_set(...)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  for i = 1, select(&amp;quot;#&amp;quot;, ...) do&lt;br /&gt;
    res[ select(i, ...) ] = true&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local space_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;)&lt;br /&gt;
local delim_chars   = create_set(&amp;quot; &amp;quot;, &amp;quot;\t&amp;quot;, &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, &amp;quot;]&amp;quot;, &amp;quot;}&amp;quot;, &amp;quot;,&amp;quot;)&lt;br /&gt;
local escape_chars  = create_set(&amp;quot;\\&amp;quot;, &amp;quot;/&amp;quot;, &#039;&amp;quot;&#039;, &amp;quot;b&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;n&amp;quot;, &amp;quot;r&amp;quot;, &amp;quot;t&amp;quot;, &amp;quot;u&amp;quot;)&lt;br /&gt;
local literals      = create_set(&amp;quot;true&amp;quot;, &amp;quot;false&amp;quot;, &amp;quot;null&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local literal_map = {&lt;br /&gt;
  [ &amp;quot;true&amp;quot;  ] = true,&lt;br /&gt;
  [ &amp;quot;false&amp;quot; ] = false,&lt;br /&gt;
  [ &amp;quot;null&amp;quot;  ] = nil,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function next_char(str, idx, set, negate)&lt;br /&gt;
  for i = idx, #str do&lt;br /&gt;
    if set[str:sub(i, i)] ~= negate then&lt;br /&gt;
      return i&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  return #str + 1&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function decode_error(str, idx, msg)&lt;br /&gt;
  local line_count = 1&lt;br /&gt;
  local col_count = 1&lt;br /&gt;
  for i = 1, idx - 1 do&lt;br /&gt;
    col_count = col_count + 1&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;\n&amp;quot; then&lt;br /&gt;
      line_count = line_count + 1&lt;br /&gt;
      col_count = 1&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;%s at line %d col %d&amp;quot;, msg, line_count, col_count) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function codepoint_to_utf8(n)&lt;br /&gt;
  -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&amp;amp;id=iws-appendixa&lt;br /&gt;
  local f = math.floor&lt;br /&gt;
  if n &amp;lt;= 0x7f then&lt;br /&gt;
    return string.char(n)&lt;br /&gt;
  elseif n &amp;lt;= 0x7ff then&lt;br /&gt;
    return string.char(f(n / 64) + 192, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0xffff then&lt;br /&gt;
    return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  elseif n &amp;lt;= 0x10ffff then&lt;br /&gt;
    return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,&lt;br /&gt;
                       f(n % 4096 / 64) + 128, n % 64 + 128)&lt;br /&gt;
  end&lt;br /&gt;
  error( string.format(&amp;quot;invalid unicode codepoint &#039;%x&#039;&amp;quot;, n) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_unicode_escape(s)&lt;br /&gt;
  local n1 = tonumber( s:sub(1, 4),  16 )&lt;br /&gt;
  local n2 = tonumber( s:sub(7, 10), 16 )&lt;br /&gt;
   -- Surrogate pair?&lt;br /&gt;
  if n2 then&lt;br /&gt;
    return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)&lt;br /&gt;
  else&lt;br /&gt;
    return codepoint_to_utf8(n1)&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_string(str, i)&lt;br /&gt;
  local res = &amp;quot;&amp;quot;&lt;br /&gt;
  local j = i + 1&lt;br /&gt;
  local k = j&lt;br /&gt;
&lt;br /&gt;
  while j &amp;lt;= #str do&lt;br /&gt;
    local x = str:byte(j)&lt;br /&gt;
&lt;br /&gt;
    if x &amp;lt; 32 then&lt;br /&gt;
      decode_error(str, j, &amp;quot;control character in string&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    elseif x == 92 then -- `\`: Escape&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      j = j + 1&lt;br /&gt;
      local c = str:sub(j, j)&lt;br /&gt;
      if c == &amp;quot;u&amp;quot; then&lt;br /&gt;
        local hex = str:match(&amp;quot;^[dD][89aAbB]%x%x\\u%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or str:match(&amp;quot;^%x%x%x%x&amp;quot;, j + 1)&lt;br /&gt;
                 or decode_error(str, j - 1, &amp;quot;invalid unicode escape in string&amp;quot;)&lt;br /&gt;
        res = res .. parse_unicode_escape(hex)&lt;br /&gt;
        j = j + #hex&lt;br /&gt;
      else&lt;br /&gt;
        if not escape_chars[c] then&lt;br /&gt;
          decode_error(str, j - 1, &amp;quot;invalid escape char &#039;&amp;quot; .. c .. &amp;quot;&#039; in string&amp;quot;)&lt;br /&gt;
        end&lt;br /&gt;
        res = res .. escape_char_map_inv[c]&lt;br /&gt;
      end&lt;br /&gt;
      k = j + 1&lt;br /&gt;
&lt;br /&gt;
    elseif x == 34 then -- `&amp;quot;`: End of string&lt;br /&gt;
      res = res .. str:sub(k, j - 1)&lt;br /&gt;
      return res, j + 1&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    j = j + 1&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  decode_error(str, i, &amp;quot;expected closing quote for string&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_number(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local s = str:sub(i, x - 1)&lt;br /&gt;
  local n = tonumber(s)&lt;br /&gt;
  if not n then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid number &#039;&amp;quot; .. s .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return n, x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_literal(str, i)&lt;br /&gt;
  local x = next_char(str, i, delim_chars)&lt;br /&gt;
  local word = str:sub(i, x - 1)&lt;br /&gt;
  if not literals[word] then&lt;br /&gt;
    decode_error(str, i, &amp;quot;invalid literal &#039;&amp;quot; .. word .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return literal_map[word], x&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_array(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  local n = 1&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local x&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of array?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;]&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read token&lt;br /&gt;
    x, i = parse(str, i)&lt;br /&gt;
    res[n] = x&lt;br /&gt;
    n = n + 1&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;]&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;]&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function parse_object(str, i)&lt;br /&gt;
  local res = {}&lt;br /&gt;
  i = i + 1&lt;br /&gt;
  while 1 do&lt;br /&gt;
    local key, val&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    -- Empty / end of object?&lt;br /&gt;
    if str:sub(i, i) == &amp;quot;}&amp;quot; then&lt;br /&gt;
      i = i + 1&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
    -- Read key&lt;br /&gt;
    if str:sub(i, i) ~= &#039;&amp;quot;&#039; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected string for key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    key, i = parse(str, i)&lt;br /&gt;
    -- Read &#039;:&#039; delimiter&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    if str:sub(i, i) ~= &amp;quot;:&amp;quot; then&lt;br /&gt;
      decode_error(str, i, &amp;quot;expected &#039;:&#039; after key&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
    i = next_char(str, i + 1, space_chars, true)&lt;br /&gt;
    -- Read value&lt;br /&gt;
    val, i = parse(str, i)&lt;br /&gt;
    -- Set&lt;br /&gt;
    res[key] = val&lt;br /&gt;
    -- Next token&lt;br /&gt;
    i = next_char(str, i, space_chars, true)&lt;br /&gt;
    local chr = str:sub(i, i)&lt;br /&gt;
    i = i + 1&lt;br /&gt;
    if chr == &amp;quot;}&amp;quot; then break end&lt;br /&gt;
    if chr ~= &amp;quot;,&amp;quot; then decode_error(str, i, &amp;quot;expected &#039;}&#039; or &#039;,&#039;&amp;quot;) end&lt;br /&gt;
  end&lt;br /&gt;
  return res, i&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local char_func_map = {&lt;br /&gt;
  [ &#039;&amp;quot;&#039; ] = parse_string,&lt;br /&gt;
  [ &amp;quot;0&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;1&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;2&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;3&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;4&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;5&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;6&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;7&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;8&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;9&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;-&amp;quot; ] = parse_number,&lt;br /&gt;
  [ &amp;quot;t&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;f&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;n&amp;quot; ] = parse_literal,&lt;br /&gt;
  [ &amp;quot;[&amp;quot; ] = parse_array,&lt;br /&gt;
  [ &amp;quot;{&amp;quot; ] = parse_object,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
parse = function(str, idx)&lt;br /&gt;
  local chr = str:sub(idx, idx)&lt;br /&gt;
  local f = char_func_map[chr]&lt;br /&gt;
  if f then&lt;br /&gt;
    return f(str, idx)&lt;br /&gt;
  end&lt;br /&gt;
  decode_error(str, idx, &amp;quot;unexpected character &#039;&amp;quot; .. chr .. &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function json.decode(str)&lt;br /&gt;
  if type(str) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
    error(&amp;quot;expected argument of type string, got &amp;quot; .. type(str))&lt;br /&gt;
  end&lt;br /&gt;
  local res, idx = parse(str, next_char(str, 1, space_chars, true))&lt;br /&gt;
  idx = next_char(str, idx, space_chars, true)&lt;br /&gt;
  if idx &amp;lt;= #str then&lt;br /&gt;
    decode_error(str, idx, &amp;quot;trailing garbage&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
  return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Loop&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
-- A naive little event loop that lets you track running coroutines and dispatches&lt;br /&gt;
-- the events they&#039;re waiting for to them.&lt;br /&gt;
&lt;br /&gt;
local EventLoop = {&lt;br /&gt;
    -- Coroutine -&amp;gt; awaited event&lt;br /&gt;
    _coros = {},&lt;br /&gt;
    running = false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_task(func)&lt;br /&gt;
    local coro = coroutine.create(func)&lt;br /&gt;
    -- false has no semantic meaning here, we just want to reserve the space for&lt;br /&gt;
    -- the coroutine in the table without saying we&#039;re awaiting anything yet.&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    self:_run_coro(coro)&lt;br /&gt;
    return coro&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:kill_task(coro)&lt;br /&gt;
    self._coros[coro] = nil&lt;br /&gt;
    if coroutine.status ~= &amp;quot;dead&amp;quot; then&lt;br /&gt;
        coroutine.close(coro)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- (internal) run the event&lt;br /&gt;
function EventLoop:_run_coro(coro, ...)&lt;br /&gt;
    assert(coroutine.status(coro) ~= &amp;quot;dead&amp;quot;)&lt;br /&gt;
    -- assert(not self.running)&lt;br /&gt;
    local old_running = self.running&lt;br /&gt;
    self.running = true&lt;br /&gt;
&lt;br /&gt;
    -- Since we&#039;re running, this isn&#039;t currently awaiting anything&lt;br /&gt;
    self._coros[coro] = false&lt;br /&gt;
    local success, ret = coroutine.resume(coro, ...)&lt;br /&gt;
    self.running = old_running&lt;br /&gt;
&lt;br /&gt;
    -- If we&#039;re not done then ret should be the kind of event we&#039;re awaiting.&lt;br /&gt;
    -- Otherwise it&#039;s an error message or something. Who cares.&lt;br /&gt;
    if not success then&lt;br /&gt;
        -- Get rid of the coro&lt;br /&gt;
        if ret then&lt;br /&gt;
            ll.OwnerSay(`Might have an error: {ret}`)&lt;br /&gt;
        end&lt;br /&gt;
        ret = nil&lt;br /&gt;
    end&lt;br /&gt;
    self._coros[coro] = ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Wake up any tasks that are waiting for this kind of event&lt;br /&gt;
function EventLoop:handle_event(name, ...)&lt;br /&gt;
    -- We may mutate self._coros, so do a clone&lt;br /&gt;
    local coros = table.clone(self._coros)&lt;br /&gt;
    ll.OwnerSay(`Handling {name}`)&lt;br /&gt;
&lt;br /&gt;
    for coro, awaited_event in coros do&lt;br /&gt;
        if coroutine.status(coro) == &amp;quot;dead&amp;quot; then&lt;br /&gt;
            -- Hmm, this coroutine is dead, prune it.&lt;br /&gt;
            self._coros[coro] = nil&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if awaited_event == name then&lt;br /&gt;
            ll.OwnerSay(`Dispatching {name} to {coro}`)&lt;br /&gt;
            self:_run_coro(coro, ...)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function EventLoop:create_event_catcher(name)&lt;br /&gt;
    local function catcher(...)&lt;br /&gt;
        ll.OwnerSay(&amp;quot;yay&amp;quot;)&lt;br /&gt;
        EventLoop:handle_event(name, ...)&lt;br /&gt;
    end&lt;br /&gt;
    return catcher&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Helper function for waiting and telling the EventLoop what event you want&lt;br /&gt;
local function await_event(kind)&lt;br /&gt;
    -- You had better do this inside a running event loop&lt;br /&gt;
    assert(EventLoop.running)&lt;br /&gt;
    -- This will yield, the EventLoop will resume us once it has&lt;br /&gt;
    -- an event we might be interested in.&lt;br /&gt;
    return coroutine.yield(kind)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Script-specific functions&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
local function hide_children()&lt;br /&gt;
    for i=2,ll.GetNumberOfPrims() do&lt;br /&gt;
        -- Hide all the child prims&lt;br /&gt;
        ll.SetLinkAlpha(i, 0, -1)&lt;br /&gt;
        ll.SetLinkPrimitiveParamsFast(i, {&lt;br /&gt;
            PRIM_SIZE, vector(0.5, 0.5, 0.5),&lt;br /&gt;
            PRIM_TEXT, &amp;quot;&amp;quot;, vector(1,1,1), 1&lt;br /&gt;
        })&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_textbox(avatar_id, message, channel)&lt;br /&gt;
    ll.TextBox(avatar_id, message, channel)&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        local resp_channel, name, id, resp = await_event(&amp;quot;listen&amp;quot;)&lt;br /&gt;
        if id ~= avatar_id or channel ~= resp_channel then&lt;br /&gt;
            -- If this isn&#039;t a message we&#039;re interested in then wait for the next event&lt;br /&gt;
            ll.OwnerSay(`{id}, {avatar_id}, {resp_channel}, {channel}`)&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return resp&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function make_json_request(url)&lt;br /&gt;
    local req_id = ll.HTTPRequest(url, {HTTP_BODY_MAXLENGTH, integer(16000)}, &amp;quot;&amp;quot;)&lt;br /&gt;
    while true do&lt;br /&gt;
        local ev_id, status, metadata, body = await_event(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
        if ev_id ~= req_id then&lt;br /&gt;
            -- Not a response for the HTTP request we were waiting on, wait for the next event.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
        return status, metadata, body&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function request_weather()&lt;br /&gt;
    -- And you can use a wrapper like this to make an async function callable within both normal and coroutine contexts.&lt;br /&gt;
    -- Normally this sort of thing would not be necessary, but here it is since the EventLoop lives in our own code,&lt;br /&gt;
    -- and isn&#039;t a property of the actual script engine.&lt;br /&gt;
    if gWeatherRequestTask then&lt;br /&gt;
        EventLoop:kill_task(gWeatherRequestTask)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    gWeatherRequestTask = EventLoop:create_task(function()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Requesting weather&amp;quot;)&lt;br /&gt;
        local status, metadata, body = make_json_request(`https://api.open-meteo.com/v1/forecast?latitude={gLatLong[1]}&amp;amp;longitude={gLatLong[2]}&amp;amp;current=temperature_2m&amp;amp;daily=temperature_2m_max`)&lt;br /&gt;
        if status ~= 200 then&lt;br /&gt;
            ll.OwnerSay(&amp;quot;Error requesting weather&amp;quot;)&lt;br /&gt;
            return&lt;br /&gt;
        end&lt;br /&gt;
        local parsed = json.decode(body)&lt;br /&gt;
&lt;br /&gt;
        local current = parsed[&amp;quot;current&amp;quot;]&lt;br /&gt;
        ll.OwnerSay(`Temperature is {current[&amp;quot;temperature_2m&amp;quot;]}C`)&lt;br /&gt;
        ll.SetText(`Temperature in {gCityName} is currently {current[&amp;quot;temperature_2m&amp;quot;]}C`, vector(1,1,1), 1)&lt;br /&gt;
&lt;br /&gt;
        -- Show the highs for each day&lt;br /&gt;
        local days = parsed[&amp;quot;daily&amp;quot;][&amp;quot;temperature_2m_max&amp;quot;]&lt;br /&gt;
        for i=1,#days do&lt;br /&gt;
            local link_num = i + 1&lt;br /&gt;
            -- Higher temps = bigger on Z, negatives are unrepresentable :)&lt;br /&gt;
            local z_size = (days[i] / 30) * 2&lt;br /&gt;
            ll.SetLinkAlpha(link_num, 1, -1)&lt;br /&gt;
            ll.SetLinkPrimitiveParamsFast(link_num, {&lt;br /&gt;
                PRIM_SIZE, vector(0.5, 0.5, z_size),&lt;br /&gt;
                PRIM_TEXT, `{days[i]}C`, vector(1,1,1), 1&lt;br /&gt;
            })&lt;br /&gt;
        end&lt;br /&gt;
    end)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ask_for_city()&lt;br /&gt;
    local resp = send_textbox(ll.GetOwner(), &amp;quot;What city do you want the weather for?&amp;quot;, TEXTBOX_CHANNEL)&lt;br /&gt;
    if not resp then&lt;br /&gt;
        ll.OwnerSay(&amp;quot;Got no response for city request.&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    ll.OwnerSay(`You want the weather for {resp}.`)&lt;br /&gt;
    gLatLong = nil&lt;br /&gt;
    gCityName = resp&lt;br /&gt;
&lt;br /&gt;
    hide_children()&lt;br /&gt;
&lt;br /&gt;
    -- request the lat and long for this city&lt;br /&gt;
    local status, metadata, body = make_json_request(`https://nominatim.openstreetmap.org/search.php?city={ll.EscapeURL(resp)}&amp;amp;format=jsonv2`)&lt;br /&gt;
    local parsed = json.decode(body)&lt;br /&gt;
    if not #parsed then&lt;br /&gt;
        ll.OwnerSay(`Got no results for {resp}!`)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local first_result = parsed[1]&lt;br /&gt;
    gLatLong = {first_result[&amp;quot;lat&amp;quot;], first_result[&amp;quot;lon&amp;quot;]}&lt;br /&gt;
    ll.OwnerSay(`City Lat: {gLatLong[1]}, Long: {gLatLong[2]}`)&lt;br /&gt;
&lt;br /&gt;
    request_weather()&lt;br /&gt;
&lt;br /&gt;
    -- request weather again in 3 mins&lt;br /&gt;
    ll.SetTimerEvent(180)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
-- Event Handlers&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- If we don&#039;t care about conventionally dispatched events, we can just set the event handler to directly send&lt;br /&gt;
-- event data to the event loop.&lt;br /&gt;
http_response = EventLoop:create_event_catcher(&amp;quot;http_response&amp;quot;)&lt;br /&gt;
listen = EventLoop:create_event_catcher(&amp;quot;listen&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- More conventional event handlers work too.&lt;br /&gt;
function touch_start(num)&lt;br /&gt;
    for i=0,num-1 do&lt;br /&gt;
        if ll.GetOwner() ~= ll.DetectedKey(i) then&lt;br /&gt;
            -- Go to next loop if it wasn&#039;t the owner touching us.&lt;br /&gt;
            continue&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        if gCityRequestTask then&lt;br /&gt;
            EventLoop:kill_task(gCityRequestTask)&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        -- Spin up a task to ask the user for their city&lt;br /&gt;
        gCityRequestTask = EventLoop:create_task(ask_for_city)&lt;br /&gt;
        break&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function timer()&lt;br /&gt;
    request_weather()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Not strictly necessary, but I like having the main function have its own scope.&lt;br /&gt;
-- Rather than just have top-level logic.&lt;br /&gt;
local function main()&lt;br /&gt;
    ll.Listen(TEXTBOX_CHANNEL, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
    ll.SetText(&amp;quot;&amp;quot;, vector(1,1,1), 1)&lt;br /&gt;
    hide_children()&lt;br /&gt;
    ll.OwnerSay(&amp;quot;Weatherbox operational!&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
main()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1217304</id>
		<title>Lua FAQ</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1217304"/>
		<updated>2024-09-18T20:17:18Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
=== Can I keep using LSL? ===&lt;br /&gt;
&lt;br /&gt;
Yep! LSL should work exactly the same as before, and a lot of testing work has been done (and is being done) to ensure that is the case. Even if you’re an LSL pro and rely on things like &amp;lt;code&amp;gt;(some_list != [])&amp;lt;/code&amp;gt; for getting the list length, or strange things like &amp;lt;code&amp;gt;((largestring=&amp;quot;&amp;quot;) + largestring)&amp;lt;/code&amp;gt;, you’re fully covered. Those still work, and now have optimized implementations.&lt;br /&gt;
&lt;br /&gt;
In fact, LSL will get better than ever before, running faster, and using less memory! The generated bytecode for an LSL script in Lua is generally half the size of a Mono script, and most strings take up half as much memory by virtue of using UTF-8 instead of UTF-16.&lt;br /&gt;
&lt;br /&gt;
We’ve taken a lot of care in writing the new LSL compiler to make the scripting experience for people who prefer LSL better than it was before.&lt;br /&gt;
&lt;br /&gt;
=== Are you going to stop adding functions / events to LSL? ===&lt;br /&gt;
&lt;br /&gt;
In the general case, no. A big part of the work we’ve done is making it easy to share functions between LSL and Lua without much extra effort. For events and functions that can be reasonably exposed to LSL (i.e., they only use simple types that LSL has anyway) then LSL should still get those.&lt;br /&gt;
&lt;br /&gt;
For more advanced features, the idea is that there should still be a path for exposing them to LSL. Obviously, with Lua it’s possible to have object-oriented APIs that would not be possible in LSL, but those objects will generally be written in a way that calling a method on a “special” object we introduce in Lua will really boil down to a function call that can be expressed in LSL.&lt;br /&gt;
&lt;br /&gt;
Not a real example, but &amp;lt;code&amp;gt;some_object:giveInventoryList(&amp;quot;folder_name&amp;quot;, inventory_list)&amp;lt;/code&amp;gt; might really boil down to a &amp;lt;code&amp;gt;llGiveInventoryList(some_object_id, &amp;quot;folder_name&amp;quot;, &amp;quot;inventory_list&amp;quot;);&amp;lt;/code&amp;gt; call. In that way, most additional Lua functionality will be a fancy “easier” wrapper around functions shared with LSL!&lt;br /&gt;
&lt;br /&gt;
The caveat is if functions are added that use new types that aren&#039;t in LSL (like functions that take in a &amp;lt;code&amp;gt;key -&amp;gt; value&amp;lt;/code&amp;gt; map) likely won&#039;t be exposed to LSL, as there&#039;s no way to represent them in LSL anyway.&lt;br /&gt;
&lt;br /&gt;
=== Wait, LSL can run on Lua now? How? Why would you do that? ===&lt;br /&gt;
&lt;br /&gt;
A reasonable question! It sounds crazy at first, but as it turns out, LSL maps really well to Lua bytecode, and we already have a compiler that can output both Mono and legacy LSO2 bytecode. Writing a compiler that could output Lua bytecode wasn&#039;t terribly difficult, and it allows LSL scripts to run faster, and be more memory-efficient. Most importantly, it allows us to test behavior of the new scripting engine against the behavior of the existing Mono scripting engine. This helps us make sure we&#039;re doing an apples-to-apples comparison on our benchmarks, and that all the nice things that Mono currently gets us will also work correctly under the new scripting engine.&lt;br /&gt;
&lt;br /&gt;
Additionally, for better or for worse, LSL is here to stay. People have been writing scripts in it for 20 years, and they&#039;re going to be running on the servers for years to come. Performance and memory usage issues with LSL scripts will continue to matter for the foreseeable future. The faster the server can run existing LSL scripts, the more time it can dedicate to running newer Lua scripts. Memory usage has been a particular problem with the existing Mono scripting engine, and this would help us address this.&lt;br /&gt;
&lt;br /&gt;
=== What about running scripts on the viewer-side if I want viewer-side effects? Does this have any interaction with the work adding Lua addons into the viewer? ===&lt;br /&gt;
&lt;br /&gt;
No, the work on adding Lua addons to the viewer is a separate effort. Lua scripts on SL will strictly run on the region, and not on the people&#039;s viewers. We&#039;re focused on providing a high-quality replacement to the existing server-side scripts before we look at anything else.&lt;br /&gt;
&lt;br /&gt;
Note that we are all too aware of the current difficulty of scripting HUD UIs for Second Life (we make a lot of our own) and we&#039;re thinking of ways we can make things better, but there are no definite plans, and it wouldn&#039;t involve running scripts on the viewer in the near-term. The current UI tooling exposed to Lua viewer addons isn&#039;t tooled for being called from untrusted code either way, so lack of &amp;quot;client-side&amp;quot; scripts is unlikely to hinder any effort to create a better API for HUDs for in-world systems.&lt;br /&gt;
&lt;br /&gt;
=== You said Lua runs LSL faster than Mono? It uses less memory? How? I&#039;m suspicious. ===&lt;br /&gt;
&lt;br /&gt;
A reasonable suspicion! We were also suspicious and suspected that Lua would be slightly slower, but in reality, yes, Lua can run LSL scripts quite a bit faster than the custom-patched Mono 2.6 we currently use. The best LSL on Mono has performed compared to Luau in our internal tests is 10% slower than Luau. Similarly, most LSL on Luau scripts end up being around half the size of Mono scripts. That means much more memory to play with in your LSL scripts.&lt;br /&gt;
&lt;br /&gt;
The reason why isn&#039;t intuitive, after all, Mono is a lot more intricate and has a lot of fancy performance optimizations like a JIT optimizer, but it becomes clear when you look at the bytecode that&#039;s generated for LSL on each scripting engine.&lt;br /&gt;
&lt;br /&gt;
Consider the following LSL function as an example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lsl2&amp;quot;&amp;gt;&lt;br /&gt;
float loudSubtract(float val, float sub) {&lt;br /&gt;
    // subtract, telling the owner what we did&lt;br /&gt;
	float new_val = val - sub;&lt;br /&gt;
	llOwnerSay(&amp;quot;New val is &amp;quot; + (string)new_val);&lt;br /&gt;
	return new_val;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This compiles to the following Luau bytecode (subject to further optimization):&lt;br /&gt;
&lt;br /&gt;
{| role=&amp;quot;presentation&amp;quot; class=&amp;quot;wikitable mw-collapsible autocollapse mw-collapsed&amp;quot;&lt;br /&gt;
| &amp;lt;strong&amp;gt;Lua bytecode&amp;lt;/strong&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre&amp;gt;&lt;br /&gt;
Function 0 (_floudSubtract):&lt;br /&gt;
SUB R2 R0 R1&lt;br /&gt;
GETIMPORT R3 2 [ll.OwnerSay]&lt;br /&gt;
GETIMPORT R6 5 [lsl.cast]&lt;br /&gt;
MOVE R7 R2&lt;br /&gt;
LOADN R8 3&lt;br /&gt;
CALL R6 2 1&lt;br /&gt;
LOADK R5 K6 [&#039;New val is &#039;]&lt;br /&gt;
CONCAT R4 R5 R6&lt;br /&gt;
CALL R3 1 0&lt;br /&gt;
RETURN R2 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Okay, that&#039;s pretty decent. Some places where things could be more optimized, but no big deal. The equivalent Mono bytecode is (and bear with us, this is long:)&lt;br /&gt;
&lt;br /&gt;
{| role=&amp;quot;presentation&amp;quot; class=&amp;quot;wikitable mw-collapsible autocollapse mw-collapsed&amp;quot;&lt;br /&gt;
| &amp;lt;strong&amp;gt;Mono bytecode&amp;lt;/strong&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre&amp;gt;&lt;br /&gt;
 .class public auto ansi serializable beforefieldinit&lt;br /&gt;
  LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    extends [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 &#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 val&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; l1&#039;&lt;br /&gt;
&lt;br /&gt;
  .method compilercontrolled specialname rtspecialname instance void&lt;br /&gt;
    .ctor(&lt;br /&gt;
      int32 _param1,&lt;br /&gt;
      class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 _param2,&lt;br /&gt;
      float32 _param3,&lt;br /&gt;
      float32 _param4,&lt;br /&gt;
      float32 _param5,&lt;br /&gt;
      int32 _param6&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 4&lt;br /&gt;
&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: call         instance void [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame::.ctor()&lt;br /&gt;
&lt;br /&gt;
    // [29 5 - 29 28]&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldarg.s      _param1&lt;br /&gt;
    IL_0009: nop&lt;br /&gt;
    IL_000a: nop&lt;br /&gt;
    IL_000b: nop&lt;br /&gt;
    IL_000c: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
    // [30 5 - 30 34]&lt;br /&gt;
    IL_0011: ldarg.0      // this&lt;br /&gt;
    IL_0012: ldarg.s      _param2&lt;br /&gt;
    IL_0014: nop&lt;br /&gt;
    IL_0015: nop&lt;br /&gt;
    IL_0016: nop&lt;br /&gt;
    IL_0017: stfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 23]&lt;br /&gt;
    IL_001c: ldarg.0      // this&lt;br /&gt;
    IL_001d: ldarg.s      _param3&lt;br /&gt;
    IL_001f: nop&lt;br /&gt;
    IL_0020: nop&lt;br /&gt;
    IL_0021: nop&lt;br /&gt;
    IL_0022: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
&lt;br /&gt;
    // [32 5 - 32 23]&lt;br /&gt;
    IL_0027: ldarg.0      // this&lt;br /&gt;
    IL_0028: ldarg.s      _param4&lt;br /&gt;
    IL_002a: nop&lt;br /&gt;
    IL_002b: nop&lt;br /&gt;
    IL_002c: nop&lt;br /&gt;
    IL_002d: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
    // [33 5 - 33 28]&lt;br /&gt;
    IL_0032: ldarg.0      // this&lt;br /&gt;
    IL_0033: ldarg.s      _param5&lt;br /&gt;
    IL_0035: nop&lt;br /&gt;
    IL_0036: nop&lt;br /&gt;
    IL_0037: nop&lt;br /&gt;
    IL_0038: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
    // [34 5 - 34 28]&lt;br /&gt;
    IL_003d: ldarg.0      // this&lt;br /&gt;
    IL_003e: ldarg.s      _param6&lt;br /&gt;
    IL_0040: nop&lt;br /&gt;
    IL_0041: nop&lt;br /&gt;
    IL_0042: nop&lt;br /&gt;
    IL_0043: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0048: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor&lt;br /&gt;
&lt;br /&gt;
  .method public virtual instance object&lt;br /&gt;
    Resume() cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 8&lt;br /&gt;
&lt;br /&gt;
    // [37 37 - 37 99]&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: ldfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
    IL_000c: ldarg.0      // this&lt;br /&gt;
    IL_000d: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
    IL_0012: call         instance float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575::gloudSubtract(float32, float32)&lt;br /&gt;
    IL_0017: box          [mscorlib]System.Single&lt;br /&gt;
    IL_001c: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::Resume&lt;br /&gt;
} // end of class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 // ... Within the actual script class&lt;br /&gt;
&lt;br /&gt;
 .method public hidebysig instance float32&lt;br /&gt;
    gloudSubtract(&lt;br /&gt;
      float32 val,&lt;br /&gt;
      float32 &#039;sub&#039;&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 16&lt;br /&gt;
    .locals init (&lt;br /&gt;
      [0] float32 num1,&lt;br /&gt;
      [1] int32 &#039;l1[34-35], num2[69-151], num2[151-152]&#039;,&lt;br /&gt;
      [2] class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame c9eec9e37575gloudSubtractFrame,&lt;br /&gt;
      [3] float32 num3&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    // [17 5 - 17 60]&lt;br /&gt;
    IL_0000: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsRestoring()&lt;br /&gt;
    IL_0005: brfalse      IL_0036&lt;br /&gt;
&lt;br /&gt;
    // [19 7 - 19 202]&lt;br /&gt;
    IL_000a: call         class [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame [UThread]LindenLab.SecondLife.UThread.UThread::Pop()&lt;br /&gt;
    IL_000f: castclass    LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0014: stloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
    // [20 7 - 20 53]&lt;br /&gt;
    IL_0015: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0016: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
    IL_001b: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [21 7 - 21 55]&lt;br /&gt;
    IL_001c: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_001d: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0022: stloc.1      // l1&lt;br /&gt;
&lt;br /&gt;
    // [22 7 - 22 55]&lt;br /&gt;
    IL_0023: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0024: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
    IL_0029: switch       (IL_0085, IL_004b)&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 58]&lt;br /&gt;
    IL_0036: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_003b: brfalse      IL_004b&lt;br /&gt;
&lt;br /&gt;
    // [33 7 - 33 15]&lt;br /&gt;
    IL_0040: ldc.i4       1 // 0x00000001&lt;br /&gt;
    IL_0045: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [34 7 - 34 20]&lt;br /&gt;
    IL_0046: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [37 5 - 37 70]&lt;br /&gt;
    IL_004b: ldarg.s      &#039;sub&#039;&lt;br /&gt;
    IL_004d: nop&lt;br /&gt;
    IL_004e: nop&lt;br /&gt;
    IL_004f: nop&lt;br /&gt;
    IL_0050: ldarg.s      val&lt;br /&gt;
    IL_0052: nop&lt;br /&gt;
    IL_0053: nop&lt;br /&gt;
    IL_0054: nop&lt;br /&gt;
    IL_0055: call         float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&lt;br /&gt;
    IL_005a: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [38 5 - 38 84]&lt;br /&gt;
    IL_005b: ldloc.0      // num1&lt;br /&gt;
    IL_005c: call         string [LslLibrary]LindenLab.SecondLife.LslRunTime::ToString(float32)&lt;br /&gt;
    IL_0061: ldstr        &amp;quot;New val is &amp;quot;&lt;br /&gt;
    IL_0066: call         string [LslUserScript]LindenLab.SecondLife.LslUserScript::Add(string, string)&lt;br /&gt;
    IL_006b: call         void [LslLibrary]LindenLab.SecondLife.Library::llOwnerSay(string)&lt;br /&gt;
&lt;br /&gt;
    // [39 5 - 39 58]&lt;br /&gt;
    IL_0070: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_0075: brfalse      IL_0085&lt;br /&gt;
&lt;br /&gt;
    // [41 7 - 41 15]&lt;br /&gt;
    IL_007a: ldc.i4       0 // 0x00000000&lt;br /&gt;
    IL_007f: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [42 7 - 42 20]&lt;br /&gt;
    IL_0080: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [45 5 - 45 17]&lt;br /&gt;
    IL_0085: ldloc.0      // num1&lt;br /&gt;
    IL_0086: ret&lt;br /&gt;
    IL_0087: nop&lt;br /&gt;
&lt;br /&gt;
    // [47 5 - 47 194]&lt;br /&gt;
    IL_0088: ldloc.1      // num2&lt;br /&gt;
    IL_0089: ldarg.0      // this&lt;br /&gt;
    IL_008a: ldarg        val&lt;br /&gt;
    IL_008e: nop&lt;br /&gt;
    IL_008f: nop&lt;br /&gt;
    IL_0090: ldarg        &#039;sub&#039;&lt;br /&gt;
    IL_0094: nop&lt;br /&gt;
    IL_0095: nop&lt;br /&gt;
    IL_0096: ldloc.0      // num1&lt;br /&gt;
    IL_0097: ldloc.1      // num2&lt;br /&gt;
    IL_0098: newobj       instance void LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor(int32, class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575, float32, float32, float32, int32)&lt;br /&gt;
    IL_009d: pop&lt;br /&gt;
&lt;br /&gt;
    // [49 5 - 49 17]&lt;br /&gt;
    IL_009e: ldloc.3      // num3&lt;br /&gt;
    IL_009f: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
While this might look unreasonably large, this is also pretty similar to how &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt; C# functions that have &amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt;s get compiled. The code required to allow a function to pause in the middle to let other things run is necessarily very large, and our Mono bytecode instrumentation is essentially a special variant of that which optimizes for cases where the function won&#039;t have to pause. Try pasting the following code into sharplab.io (while decompiling to IL) and you&#039;ll see something very similar:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
using System.Threading.Tasks;&lt;br /&gt;
public class Example {&lt;br /&gt;
    public async Task llOwnerSay(string val)&lt;br /&gt;
    {&lt;br /&gt;
        // stub&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    public async Task&amp;lt;float&amp;gt; loudSubtract(float val, float sub) {&lt;br /&gt;
        float new_val = val - sub;&lt;br /&gt;
        await llOwnerSay(&amp;quot;New val is &amp;quot; + new_val.ToString());&lt;br /&gt;
        return new_val;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Lua bytecode ends up being a lot smaller, because the Lua scripting engine itself knows how to handle this case, and doesn&#039;t need to add any special code to your scripts to allow them to be paused and resumed in the middle of a function.&lt;br /&gt;
&lt;br /&gt;
Even though Mono has a JIT optimizer, which should in theory make the script run faster, this sort of code is very unusual, and the JIT optimizer isn&#039;t able to optimize it very well. That means that even though Luau itself is in theory slower, it&#039;s able to run LSL much faster than Mono can. &lt;br /&gt;
&lt;br /&gt;
You might also notice things like call &amp;lt;code&amp;gt;float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&amp;lt;/code&amp;gt; in the Mono code. Those are necessary because of LSL&#039;s weird operand evaluation order, but are unnecessary in Luau because of the more efficient form of bytecode that Luau uses.&lt;br /&gt;
&lt;br /&gt;
In general, C# doesn&#039;t aim at producing very small bytecode for async functions, which is important for something like Second Life where you might have thousands of different individual scripts running within a region. Luau does generate small bytecode, while still having decent performance, making it a good choice for Second Life. And hey, strings are UTF-8 in Lua instead of UTF-16 as in Mono, so they use much less memory for most data as well.&lt;br /&gt;
&lt;br /&gt;
=== Are we going to get a higher memory limit in scripts? ===&lt;br /&gt;
&lt;br /&gt;
Not at first, Luau uses less memory than Mono, so you shouldn&#039;t really miss it. You&#039;re not going to start hitting script memory limits just because you converted your script to Lua. Using Lua proper should allow you to more precisely control memory usage in your scripts, e.g. by being able to handle memory allocation errors in &amp;lt;code&amp;gt;pcall()&amp;lt;/code&amp;gt; and access to the mutable &amp;lt;code&amp;gt;buffer&amp;lt;/code&amp;gt; class for byte-packing.&lt;br /&gt;
&lt;br /&gt;
There is some interest in giving specific scripts a little more memory if they&#039;re designated &amp;quot;high priority&amp;quot;, like a main &amp;quot;game manager&amp;quot; object for an experience, but there are no specific plans for this at the moment.&lt;br /&gt;
&lt;br /&gt;
=== Are my existing LSL scripts going to run on Lua Automatically? ===&lt;br /&gt;
&lt;br /&gt;
While we can test running LSL in Lua now, and the idea is to eventually phase out Mono, there’s no immediate plans to transparently migrate existing scripts over to Lua. There are ideas about how this could be done safely without breaking existing content, but we want to make sure we have everything right before we start work on this.&lt;br /&gt;
&lt;br /&gt;
=== How does interoperation with LSL work? ===&lt;br /&gt;
&lt;br /&gt;
Everything that you expect from LSL should have some equivalent in Lua. There’s &amp;lt;code&amp;gt;key&amp;lt;/code&amp;gt;s, &amp;lt;code&amp;gt;rotation&amp;lt;/code&amp;gt;s and &amp;lt;code&amp;gt;vector&amp;lt;/code&amp;gt;s and something like &amp;lt;code&amp;gt;some_vec * some_rot&amp;lt;/code&amp;gt; in LSL is the exact same in Lua scripts.&lt;br /&gt;
&lt;br /&gt;
All LSL functions will be available in Lua, and something like &amp;lt;code&amp;gt;llSay(0, “hi!”)&amp;lt;/code&amp;gt; is just &amp;lt;code&amp;gt;ll.Say(0, “hi!”)&amp;lt;/code&amp;gt; now. What about &amp;lt;code&amp;gt;llSetPos(llGetPos() + &amp;lt;0, 0, 1&amp;gt;);&amp;lt;/code&amp;gt;? Just &amp;lt;code&amp;gt;ll.SetPos(ll.GetPos() + vector(0, 0, 1))&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
No special magic required here, and you can write whatever fancy wrappers you want around the existing LSL functions. We’ll be looking at how residents actually write these wrappers to inform the design of “official” higher-level Lua APIs, so we’re excited to see your ideas!&lt;br /&gt;
&lt;br /&gt;
=== Can I mix LSL and Lua in the same script? ===&lt;br /&gt;
&lt;br /&gt;
No, although most things should be a direct translation. At some point we may make a tool to help you do this, but there are no specific plans at the moment.&lt;br /&gt;
&lt;br /&gt;
If your object has multiple scripts, you can mix LSL and Lua within the same object, though.&lt;br /&gt;
&lt;br /&gt;
=== Can we use all of Lua? Is anything not there? ===&lt;br /&gt;
&lt;br /&gt;
Most everything will be there, except for two notable exceptions:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;getfenv()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;setfenv()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;_ENV&amp;lt;/code&amp;gt; are not accessible because they necessarily make the script harder to optimize. Usually people only use this for mocking in test code, and this can be accomplished by passing in a parameter with a table of function references rather than pretending you have different global variable values for certain functions.&lt;br /&gt;
* &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; is not supported since it has a huge impact on performance. For those not familiar, this is equivalent to eval() in other languages. Every single time you call &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; the compiler has to go over what you passed in, compile it, and then load the generated bytecode. The Lua compiler isn’t particularly fast, and doing this can cause your script to halt in a way that we can’t interrupt to give other scripts time to run. This is a big part of why &amp;lt;code&amp;gt;eval()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; are generally considered poor programming practice.&lt;br /&gt;
&lt;br /&gt;
The one real use-case we could think of for &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; is a debugger, and we’re looking at implementing a “real” debugger that lets you step through your code. Note that this won’t happen with the initial release, but watch this space!&lt;br /&gt;
&lt;br /&gt;
One thing that is planned, but will likely not be in the initial release is the coroutine library. This is one we’d really like to have in there, so you can do fancy things like have multiple “timers” in a script by just having coroutines that sleep every so often, but multiple running coroutines within the same script doesn’t fit well with the existing scripting architecture the Second Life server uses. Rest assured, this will be in there soon™ after Lua is released, but in the meantime you can still use something like the &amp;lt;code&amp;gt;Promise&amp;lt;/code&amp;gt; chaining that JavaScript uses to write asynchronous event handlers.&lt;br /&gt;
&lt;br /&gt;
We’ll have code demonstrating &amp;quot;proper&amp;quot; usage of Lua around the time that the public beta starts.&lt;br /&gt;
&lt;br /&gt;
=== Why Lua? I wanted C#! ===&lt;br /&gt;
&lt;br /&gt;
Great question!&lt;br /&gt;
&lt;br /&gt;
The server Lua scripting initiative actually started off as an attempt to get C# working on Second Life, basically as a revival of the work started in the 2000s. The initial thought was “this should be pretty easy, most of the work was already done getting everything onto Mono!” A lot of effort was put in over several months to:&lt;br /&gt;
&lt;br /&gt;
* Understand the Mono and .NET Core runtimes&lt;br /&gt;
* Observe how other Engines and Virtual Worlds have embedded C# and scripting in general&lt;br /&gt;
* Understand how recent advancements in scripting technologies (like WebAssembly) might make this integration easier&lt;br /&gt;
* Whether unique properties of how scripting / content creation works on Second Life would make this harder to implement&lt;br /&gt;
&lt;br /&gt;
Unfortunately, the reasons we were ultimately unable to do this were very technical, so there’s no side-stepping a whole lot of tech-speak here, but there were several key properties that made integrating C# in Second Life rather difficult to implement:&lt;br /&gt;
&lt;br /&gt;
==== Sandboxing with reflection is hard ====&lt;br /&gt;
&lt;br /&gt;
Previous proof-of-concept code for C# in Second Life relied on CoreCLR sandboxing in Mono. This approach has fallen out of favor in recent years, because similar approaches led to many, many security holes in Java’s browser plugin. This sandboxing method is not possible in .NET Core, and has been totally removed.&lt;br /&gt;
Since this sandboxing is no longer in .NET, we need to rely on the ability to look at code to determine if it looks “dangerous” before running it. Some people have done this, but it can only be done properly by disabling reflection, which is a core part of C# and .NET! Without reflection, you’re unable to use the fancy third-party Newtonsoft.JSON libraries or whatnot that you would have otherwise been able to use.&lt;br /&gt;
&lt;br /&gt;
Big portions of .NET’s standard library use reflection in a way that would be unsafe to expose to residents for scripting facilities, so you essentially have to maintain a list of functions and classes that are “safe” to use, and write alternatives to functions that are “unsafe” to call, but provide useful functionality. At this point, the scripting language that you’re exposing is a pretty bare-bones C# that needs to be scripted fundamentally differently from how you would write C# in Unity, for example.&lt;br /&gt;
&lt;br /&gt;
Allowing the use of modern C# features would require migration off of Mono and onto .NET core, which would be more involved than integrating Lua. Mono is in maintenance mode at the moment, and only supports the legacy .NET framework&lt;br /&gt;
Ensuring that we’re able to correctly limit each script’s memory usage in a low-impact way is a bit of an unsolved problem in .NET Core. Generally, memory allocation tracking is something that you only do for debugging, but we need to do it for all scripts! What happens if you declare a static global in a C# script? Is the memory usage counted against all scripts of that type? Do all scripts share the data in that static variable?&lt;br /&gt;
&lt;br /&gt;
What about if there’s a function in the standard library that keeps a static cache of data? How do we penalize (or not penalize) scripts for allocations that they didn’t even control? How do we make sure that data remains consistent across script unloads / reloads?&lt;br /&gt;
&lt;br /&gt;
These are problems that most people embedding C# don’t need to solve, and the solutions that do exist are not generally workable into Second Life’s scripting model without multiple months of work. Most other virtual worlds do script sandboxing on a per-parcel or per-region level and put everything into the same logical scripting sandbox, which Second Life does not do. People would be rightly annoyed if someone’s HUD or attachments caused their parcel to go over the memory limit and things stopped working.&lt;br /&gt;
&lt;br /&gt;
==== Making everything micro-threaded with extreme size constraints is hard ====&lt;br /&gt;
&lt;br /&gt;
We need to be able to temporarily pause scripts in the middle of running an event handler so we can ensure we fairly schedule code execution. Previously, we wrote a script to do some quite clever binary instrumentation of .NET assemblies to inject yield points. This works quite well for for LSL scripts, but there are issues with C# in particular.&lt;br /&gt;
&lt;br /&gt;
One is that it doesn’t handle the &amp;lt;code&amp;gt;try&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;finally&amp;lt;/code&amp;gt; construct in C#. While not impossible to handle, this would be a difficult undertaking. Something that would be much more difficult is allowing pausing execution inside a constructor or other such special constructs in C#. While the stack induction and bytecode manipulation done by our current system is very cool, it is quite complex to update, relies on outdated tools that don’t work with new .NET versions, and necessarily requires making the bytecode quite a bit larger than it would otherwise be by injecting hooks.&lt;br /&gt;
&lt;br /&gt;
But that’s not a huge deal, our .NET instrumentation library was written when &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt; was not a thing in C#, so we should be able to inject &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt; into user-provided source code instead to make it possible to transparently pause scripts right? Ehhhh, sort of. There are certain functions that cannot be made &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt; in C# without breaking things. Object constructors are one of those, we have no way to pause and resume inside those. The situation is similar inside &amp;lt;code&amp;gt;ToString()&amp;lt;/code&amp;gt; overloads. If we make a user-provided &amp;lt;code&amp;gt;ToString()&amp;lt;/code&amp;gt; overload async, it’ll just never get called in cases where ToString() would normally get called! And what about implementing &amp;lt;code&amp;gt;IEnumerable.GetIterator()&amp;lt;/code&amp;gt;? That might work… if you did a lot of work of replacing &amp;lt;code&amp;gt;IEnumerable&amp;lt;/code&amp;gt; with &amp;lt;code&amp;gt;IAsyncEnumerable&amp;lt;/code&amp;gt;., and &amp;lt;code&amp;gt;foreach&amp;lt;/code&amp;gt; with &amp;lt;code&amp;gt;await foreach&amp;lt;/code&amp;gt;... sometimes.&lt;br /&gt;
&lt;br /&gt;
Suffice to say, that if you go this route, you end up with something that’s sort-of C# but is missing a lot of things people expect of C#, and may not behave the same as the regular C# that it pretends to be.&lt;br /&gt;
&lt;br /&gt;
==== Saving script state ====&lt;br /&gt;
&lt;br /&gt;
But let’s not forget one of the most important cases where SL is unique: Scripts can move into your inventory, across region boundaries, etc, without having to care too much that it even happened. Even if your script runs forever in a single function, that’s totally fine! For example, this is a valid LSL script:&lt;br /&gt;
&lt;br /&gt;
  default {&lt;br /&gt;
    state_entry() {&lt;br /&gt;
      integer i;&lt;br /&gt;
      while(TRUE) {&lt;br /&gt;
        llSetText(&amp;quot;Run Number &amp;quot; + (string)(i++), &amp;lt;1,1,1&amp;gt;, 1);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
All it does is run forever in the same function, incrementing a number in the floating text above an object. It still works if you take the object into your inventory, it still works if you move the object to another region, as a scripter you don&#039;t really have to care except in limited circumstances.&lt;br /&gt;
&lt;br /&gt;
That&#039;s a pretty contrived example, but there are tons of scripts with a structure like this! Probably a more common example is a math-heavy function that just takes a while to run. Under the current model, you can interrupt what&#039;s going on by just picking up the object, then plop it back down later and let it continue on. There are a number of ways you can handle this without serializing the whole runtime state, like raising an exception in the middle of executing the function, but what are the chances that people will handle this correctly? How many command-line scripts have you used that do something really weird when you pressed &amp;lt;code&amp;gt;CTRL+C&amp;lt;/code&amp;gt;?&lt;br /&gt;
&lt;br /&gt;
To keep things simple for creators, we wanted to keep this nice property of LSL in whatever language we chose. This seemingly simple feature requires a lot of work because, think about it, how many programming languages let you seamlessly halt a program in the middle of it doing something, move it over to another computer, then resume whatever it was doing? Can you do that without doing a full-memory snapshot that would be gigabytes in size? That&#039;s not really a thing that people generally do, but it&#039;s required for us to not have sharp edges on our script creation experience!&lt;br /&gt;
&lt;br /&gt;
We have a version of this in our Mono scripting engine, which was all written in-house. It works very well for LSL, but it has trouble with more complex structures. As it turns out, taking all the state of a running program, serializing it, then transparently deserializing it is a difficult problem, even with data that &amp;lt;i&amp;gt;looks&amp;lt;/i&amp;gt; simple.&lt;br /&gt;
&lt;br /&gt;
Suppose we have something like &amp;lt;code&amp;gt;Dictionary&amp;lt;SomeObject, int&amp;gt;&amp;lt;/code&amp;gt; that we use as a counter for how many times we&#039;ve seen each &amp;lt;code&amp;gt;SomeObject&amp;lt;/code&amp;gt;. What do you do if the &amp;lt;code&amp;gt;GetHashCode()&amp;lt;/code&amp;gt; bucketing changes after the script deserializes and the memory increases? Can you prevent that from happening? Will live &amp;lt;code&amp;gt;IEnumerator&amp;lt;/code&amp;gt;s still work correctly? How can you be sure of this? .NET makes no guarantees about any of this behavior, and good luck digging through the .NET source code to figure out if any of this is even the case.&lt;br /&gt;
&lt;br /&gt;
What about types that aren&#039;t marked &amp;lt;code&amp;gt;[Serializable]&amp;lt;/code&amp;gt;, including types in the standard library? Can people just not use those? Do we have to write special custom serializers for them? Could they just randomly break when the script is deserialized and we accept that as a consequence?&lt;br /&gt;
&lt;br /&gt;
As you can see, there are a lot of hidden sharp edges involved in transparently moving scripts across servers, and the general answer provided when you ask about doing this is &amp;quot;That&#039;s very hard, make users write more intricate scripts that explicitly handle their own serialization and save their own state.&amp;quot; This is fine if most of your users are professional programmers, not so if you have a mix of professional programmers and regular users who just want an object that counts how many people have clicked on their head.&lt;br /&gt;
&lt;br /&gt;
==== Wrapping it up ====&lt;br /&gt;
&lt;br /&gt;
This is a very small subset of the issues we ran into while trying to design a satisfactory C#-based scripting system that fits Second Life&#039;s needs. Seriously, there&#039;s about 20 pages of notes.&lt;br /&gt;
&lt;br /&gt;
Meanwhile, Lua makes most of these things easy. Want to halt a script mid-execution? Okay, set an interrupt handler. Want to serialize your script while it&#039;s running and have it move to another server? Okay, there are libraries that have existed for decades that can do this. Fable&#039;s save file system was based on this. Quite a lot of scripting engine integration problems we have that would rise to the level of extremely difficult and relatively novel Computer Science research in .NET (and by extension, C#) are relatively simple to do with Lua.&lt;br /&gt;
&lt;br /&gt;
=== Okay, but seriously, why Lua specifically? Because it&#039;s popular for games? Because &amp;lt;other platform&amp;gt; uses it? Why not &amp;lt;X&amp;gt;? ===&lt;br /&gt;
&lt;br /&gt;
Actually, no. Neither other users of Lua, or a specific love of the language had anything to do with the decision. If you prefer a different programming language, there&#039;s a good chance we considered it. We actually looked at quite a lot of different languages and their associated engines, including JavaScript and WebAssembly, Lua was considered fairly late in the process. The ones below are just a small subset of the ones that we looked at:&lt;br /&gt;
&lt;br /&gt;
[[File:Lsl_vm_comparison.png|1000px]]&lt;br /&gt;
&lt;br /&gt;
As you can see, Luau was the only scripting runtime that fulfilled all of our requirements for a scripting engine within Second Life, and we arrived at it quite late in the decision process. We wanted something that we knew could deliver a high-quality scripting experience to creators, and we believe that Lua can best provide it.&lt;br /&gt;
&lt;br /&gt;
=== I genuinely hate Lua and refuse to use it ===&lt;br /&gt;
&lt;br /&gt;
A lot of people feel the same about LSL! Kidding aside, this is a totally defensible position, Lua can be a quirky language. Yes, it&#039;s strange that array indices start at 1. Yes, sometimes you can forget that you join strings with &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt;. It can take some getting used to.&lt;br /&gt;
&lt;br /&gt;
A big part of the reason why we&#039;re using Luau is due to the quality of the scripting engine itself, not specifically the language. A lot of issues people had with older versions of Lua have been addressed in Luau, and ones that haven&#039;t been for one reason or another can be easily dealt with since we have such a high degree of control over the scripting engine now. Luau is very easy to modify. If we find that there are real quality-of-life issues with Lua that people run into while writing scripts, we can just change how it works, assuming the benefits outweigh the cost of breaking interoperability with Lua scripts from elsewhere.&lt;br /&gt;
&lt;br /&gt;
But hey, maybe you really still hate the idea of using Lua. That&#039;s fine! The nice thing about having Lua instead of just LSL is there are a number of languages that can compile down to Lua code that could potentially work in Second Life! People have done work to get TypeScript and C# compiling to Lua, and it works pretty well for them, and there&#039;s also more exotic languages like MoonScript that target Lua specifically. See this GitHub repo for a listing: https://github.com/hengestone/lua-languages&lt;br /&gt;
&lt;br /&gt;
If you&#039;re so inclined, you can go as far as to take the Luau transpiler and hack it up to support &amp;lt;code&amp;gt;!=&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;//&amp;lt;/code&amp;gt; comments and really whatever you want. So long as it can take that source and output it to standard Lua, you&#039;re good.&lt;br /&gt;
&lt;br /&gt;
=== Are you going to be using Lua 5.1? Why not 5.4? ===&lt;br /&gt;
&lt;br /&gt;
We&#039;re using Luau as the base, which is &#039;&#039;compatible&#039;&#039; with Lua 5.1 but not necessarily Lua 5.1. It includes various features of PUC-Rio&#039;s Lua implementation up to and including 5.4. The Luau Compatibility reference is a good guide for which features are taken from which versions of PUC-Rio Lua: https://luau-lang.org/compatibility&lt;br /&gt;
&lt;br /&gt;
=== Are there going to be integer types in SL&#039;s Lua implementation? ===&lt;br /&gt;
&lt;br /&gt;
We have added 32-bit integers but they are strictly for compatibility with existing LSL APIs where integer type identity is important within a list (&amp;lt;code&amp;gt;llSetPrimitiveParams()&amp;lt;/code&amp;gt; and friends.) They don&#039;t have a literal form in Luau scripts and in general you should just use regular Lua numbers if you care about performance, since the Luau VM optimizes for their use, but technically you can use them. The Luau project discussions have some explanation as to why integers aren&#039;t a first-class user-facing type in Luau https://github.com/luau-lang/luau/discussions/242#discussioncomment-1707387.&lt;br /&gt;
&lt;br /&gt;
==== Are integer operations supported at all, then? (i.e. bitwise operators) ====&lt;br /&gt;
&lt;br /&gt;
Yes, through the &amp;lt;code&amp;gt;bit32&amp;lt;/code&amp;gt; library. In fact, &#039;&#039;more&#039;&#039; integer operations than are currently supported in LSL, like logical shift right and rotate operations. See https://luau-lang.org/library#bit32-library .&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217100</id>
		<title>Third Party Libraries</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217100"/>
		<updated>2024-08-11T05:40:11Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
The following table lists libraries and other third party (3p) dependencies used in constructing the Second Life Viewer.&lt;br /&gt;
&lt;br /&gt;
Most of these (all the open source ones) should be available as prebuilds packaged by and for use with [[Autobuild]].&lt;br /&gt;
&lt;br /&gt;
==== Generic Layout (Single-branch, 2024 Style) ====&lt;br /&gt;
&lt;br /&gt;
3p packages should vendor upstream contents using [https://git-scm.com/book/en/v2/Git-Tools-Submodules git submodules] unless a source git repository for the source dependency is not available.&lt;br /&gt;
A typical 3p package layout looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
.&lt;br /&gt;
├── [vendor submodule]/ - Git submodule containing upstream source&lt;br /&gt;
├── .github/            - CI/CD and repository configuration&lt;br /&gt;
├── autobuild.xml       - Autobuild manifest&lt;br /&gt;
├── build-cmd.sh        - Script used to build the repository&lt;br /&gt;
├── LICENSE             - Repository licensing&lt;br /&gt;
└── README.md           - Repository readme&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Contributor Instructions =====&lt;br /&gt;
&lt;br /&gt;
The general workflow for contributing changes to a 3p library looks like this:&lt;br /&gt;
&lt;br /&gt;
# Fork the repository and make changes. Be sure to test your work locally.&lt;br /&gt;
# Create a PR (Be sure to follow [https://gist.github.com/mikepea/863f63d6e37281e329f8 PR Etiquette] and our [https://github.com/secondlife/.github/blob/main/CODE_OF_CONDUCT.md Code of Conduct])&lt;br /&gt;
# Have this PR reviewed and merged&lt;br /&gt;
# Done! The maintainer should cut a new version as appropriate using the workflow described below.&lt;br /&gt;
&lt;br /&gt;
===== Maintainer Instructions =====&lt;br /&gt;
&lt;br /&gt;
[[File:Screenshot 2024-08-10 at 11-48-23 Releases · secondlife 3p-zlib-ng.png|thumb|top|right|A properly versioned and promoted release of 3p-zlib-ng]]&lt;br /&gt;
&lt;br /&gt;
If you are a 3p package maintainer you will want to follow these instructions to review and release new versions:&lt;br /&gt;
&lt;br /&gt;
# Review PRs quickly, and merge them directly into the default branch once approved&lt;br /&gt;
# Cut a new release by using Github&#039;s Releases feature:&lt;br /&gt;
## Navigate to &#039;&#039;&#039;Repository -&amp;gt; Releases&#039;&#039;&#039; and click &#039;&#039;&#039;&amp;quot;Draft new Release&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
## Click &#039;&#039;&#039;Choose a tag&#039;&#039;&#039; and create a new tag using the &amp;lt;code&amp;gt;vUPSTREAM-rRELEASE&amp;lt;/code&amp;gt; format described below&lt;br /&gt;
## Click &#039;&#039;&#039;Generate release notes&#039;&#039;&#039; and edit them as appropriate. Keep the title of the release equal to the version being used.&lt;br /&gt;
## Click &#039;&#039;Publish release&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
After the release is published, CI/CD should be triggered by the addition of the new tag, and provide a nice set of packages and instructions for consumers to use.&lt;br /&gt;
&lt;br /&gt;
===== Version format =====&lt;br /&gt;
&lt;br /&gt;
3p packages should be released using the following version schema:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Format&lt;br /&gt;
&lt;br /&gt;
  vUPSTREAM-rRELEASE&lt;br /&gt;
&lt;br /&gt;
  Example: v1.0.0-r2 (Second vendored release of v1.0.0 of this library)&lt;br /&gt;
&lt;br /&gt;
  UPSTREAM - Upstream package version. ex. 1.0.0 or 6.7&lt;br /&gt;
  RELEASE - Vendored release number, ex. 1, 2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Patches =====&lt;br /&gt;
&lt;br /&gt;
By convention, patches should be kept in a &amp;lt;code&amp;gt;patches/*&amp;lt;/code&amp;gt; directory and applied to the upstream source during build. You can use the following utility function, declared in your &#039;&#039;&#039;build-cmd.sh&#039;&#039;&#039; script to help:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
apply_patch()&lt;br /&gt;
{&lt;br /&gt;
    local patch=&amp;quot;$1&amp;quot;&lt;br /&gt;
    local path=&amp;quot;$2&amp;quot;&lt;br /&gt;
    echo &amp;quot;Applying $patch...&amp;quot;&lt;br /&gt;
    git apply --check --reverse --directory=&amp;quot;$path&amp;quot; &amp;quot;$patch&amp;quot; || git apply --directory=&amp;quot;$path&amp;quot; &amp;quot;$patch&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&#039;&#039;Patch helper from [https://github.com/secondlife/3p-boost 3p-boost]&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
===== Differences from old style (vendor branch) =====&lt;br /&gt;
&lt;br /&gt;
Originally, 3p libraries were maintained using a separate &#039;&#039;&#039;vendor&#039;&#039;&#039; and default branch. Repositories using this style should be migrated to new single-branch format. Benefits of using a single-branch are:&lt;br /&gt;
&lt;br /&gt;
* Simpler PR process (Make PR against default, merge it. Done.)&lt;br /&gt;
* Faster release process, which means quicker integration for package consumers&lt;br /&gt;
* Easier integration with GitHub functionality such as Automatic Release Notes&lt;br /&gt;
* More explicit maintenance of patches makes delta from upstreams clearer and simpler to understand&lt;br /&gt;
&lt;br /&gt;
=== List of third party libraries ===&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;3&amp;quot;&lt;br /&gt;
|- style=&amp;quot;background:#ffdead;&amp;quot;&lt;br /&gt;
!Library&lt;br /&gt;
!Repository&lt;br /&gt;
!License&lt;br /&gt;
!Upstream Home&lt;br /&gt;
!Description&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|apr_suite&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/ 3p-apr]&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/blob/main/apr/LICENSE Apache License]&lt;br /&gt;
|http://apr.apache.org/&lt;br /&gt;
|&amp;quot;A set of C functions which provide a portable and consistent interface into operating system - or simply widely useful - functionality. We primarily use it for threading and socket i/o. This is actually three packages, apr, apr-util and apr-iconv.&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|boost&lt;br /&gt;
|[https://github.com/secondlife/3p-boost 3p-boost]&lt;br /&gt;
|[https://github.com/boostorg/boost/blob/master/LICENSE_1_0.txt Boost License]&lt;br /&gt;
|http://www.boost.org/&lt;br /&gt;
|A set of portable C++ libraries which provide a wide set of functionality. Used primarily for tokenization.&lt;br /&gt;
|-&lt;br /&gt;
|curl&lt;br /&gt;
|[https://github.com/secondlife/3p-curl 3p-curl]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-curl/blob/master/curl/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
| http://curl.haxx.se/libcurl/&lt;br /&gt;
|Handles moving data across the net in many different protocols. Used to GET/POST/PUT/DELETE web resources.&lt;br /&gt;
|-&lt;br /&gt;
|expat&lt;br /&gt;
|[https://github.com/secondlife/3p-expat 3p-expat]&lt;br /&gt;
|[https://github.com/libexpat/libexpat/blob/master/COPYING MIT License]&lt;br /&gt;
|https://github.com/libexpat/libexpat&lt;br /&gt;
|XML parser.&lt;br /&gt;
|-&lt;br /&gt;
|freetype&lt;br /&gt;
|[https://github.com/secondlife/3p-freetype 3p-freetype]&lt;br /&gt;
|[https://github.com/freetype/freetype/blob/master/LICENSE.TXT Freetype License and Others]&lt;br /&gt;
|http://www.freetype.org/&lt;br /&gt;
|Font engine.&lt;br /&gt;
|-&lt;br /&gt;
|glh_linear&lt;br /&gt;
|[https://github.com/secondlife/3p-glh_linear 3p-glh_linear]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|nVidia NVParse SDK: platform-independent C++ Apple OpenGL helper library&lt;br /&gt;
|-&lt;br /&gt;
|gstreamer&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-gstreamer 3p-gstreamer]&lt;br /&gt;
|Open&lt;br /&gt;
|http://gstreamer.freedesktop.org&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|havok&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|jpeglib&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-jpeglib 3p-jpeglib]&lt;br /&gt;
|Open&lt;br /&gt;
|http://www.ijg.org/&lt;br /&gt;
|JPEG decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|kdu&lt;br /&gt;
|[https://hg.lindenlab.com/lindenlab/3p-kdu-private 3p-kdu-private]&amp;lt;sup&amp;gt;[[#PrivateRepo|LL]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|Commercial&lt;br /&gt;
|http://www.kakadusoftware.com&lt;br /&gt;
|Kakadu (KDU) JPEG-2000 decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|libpng&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-libpng 3p-libpng]&lt;br /&gt;
|Open&lt;br /&gt;
| http://www.libpng.org/pub/png/libpng.html&lt;br /&gt;
|PNG image library.&lt;br /&gt;
|-&lt;br /&gt;
|libxml2&lt;br /&gt;
|[https://github.com/secondlife/3p-libxml2 3p-libxml2]&lt;br /&gt;
|[https://gitlab.gnome.org/GNOME/libxml2/-/blob/master/Copyright?ref_type=heads MIT License]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|ndofdev&lt;br /&gt;
|[https://github.com/secondlife/3p-libndofdev 3p-libndofdev]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-libndofdev/blob/master/libndofdev/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|Provides joystick driver support (mac and win) for the 3DConnexion SpaceNavigator.&lt;br /&gt;
|-&lt;br /&gt;
|ogg_vorbis&lt;br /&gt;
|[https://github.com/secondlife/3p-ogg_vorbis 3p-ogvorbis]&lt;br /&gt;
|&#039;&#039;[https://github.com/xiph/ogg/blob/master/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://xiph.org/ogg/&lt;br /&gt;
|Audio codec and playback.&lt;br /&gt;
|-&lt;br /&gt;
|openal&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft 3p-openal-soft]&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft/blob/main/openal-soft/COPYING GPL]&lt;br /&gt;
|http://connect.creativelabs.com/openal/default.aspx&lt;br /&gt;
|3D Spatial audio&lt;br /&gt;
|-&lt;br /&gt;
|openjpeg&lt;br /&gt;
|[https://github.com/secondlife/3p-openjpeg 3p-openjpeg]&lt;br /&gt;
|&#039;&#039;[https://github.com/uclouvain/openjpeg/blob/master/LICENSE BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://www.openjpeg.org/&lt;br /&gt;
|An open-source JPEG-2000 library; a slower alternative to Kadaku. Used in the open source release&lt;br /&gt;
|-&lt;br /&gt;
|openssl&lt;br /&gt;
|[https://github.com/secondlife/3p-openssl 3p-openssl]&lt;br /&gt;
|[https://github.com/openssl/openssl/blob/master/LICENSE.txt Apache License]&lt;br /&gt;
|http://www.openssl.org/&lt;br /&gt;
|Provides encryption for sensitive actions such as user login&lt;br /&gt;
|-&lt;br /&gt;
|sdl&lt;br /&gt;
|[https://github.com/secondlife/3p-sdl2 3p-sdl2]&lt;br /&gt;
|[https://github.com/libsdl-org/SDL/blob/main/LICENSE.txt zlib License]&lt;br /&gt;
|http://www.libsdl.org/index.php&lt;br /&gt;
|The Simple DirectMedia Layer libraries are used for handling input and basic window/GL setup on the Linux client.&lt;br /&gt;
|-&lt;br /&gt;
|slvoice&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|This is the Vivox provided executable (renamed SLVoice)&lt;br /&gt;
|-&lt;br /&gt;
|tut&lt;br /&gt;
|[https://github.com/secondlife/3p-tut 3p-tut]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-tut/blob/master/LICENSE.txt BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://tut-framework.sourceforge.net/&lt;br /&gt;
|Unit test framework based on the use of c++ meta template programming. Used to test LL libraries.&lt;br /&gt;
|-&lt;br /&gt;
|xmlrpc_epi&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi 3p-xmlrpc-epi]&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi/blob/master/xmlrpc-epi/COPYING Epinions, Inc License]&lt;br /&gt;
|http://xmlrpc-epi.sourceforge.net/&lt;br /&gt;
|XML-RPC protocol utility&lt;br /&gt;
|-&lt;br /&gt;
|zlib-ng&lt;br /&gt;
|[https://github.com/secondlife/3p-zlib-ng 3p-zlib-ng]&lt;br /&gt;
|[https://github.com/zlib-ng/zlib-ng/blob/develop/LICENSE.md zlib License]&lt;br /&gt;
|http://www.zlib.net/&lt;br /&gt;
|zlib is a lossless data-compression library which is used for a variety of network i/o and file compression.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Redistribution =&lt;br /&gt;
Some files in the SL installation can&#039;t be freely redistributed. This means it&#039;s not possible to simply package and offer for download a modified viewer: some files &#039;&#039;&#039;must not&#039;&#039;&#039; be redistributed. &lt;br /&gt;
&lt;br /&gt;
This page documents the licenses and redistribution requirements of various files used by the viewer.&lt;br /&gt;
&lt;br /&gt;
== Windows ==&lt;br /&gt;
These are the files that need to be shipped with the Windows version&lt;br /&gt;
&lt;br /&gt;
=== Runtime ===&lt;br /&gt;
Microsoft components. It&#039;s not yet known whether they&#039;re redistributable.&lt;br /&gt;
&lt;br /&gt;
* dbghelp.dll: [http://msdn2.microsoft.com/EN-US/library/ms679294.aspx Redistributable]&lt;br /&gt;
* msvcp71.dll: C++ Runtime Library, redistributable    -viewer now using msvcp80.dll when compiled with VC2005&lt;br /&gt;
* msvcr71.dll: C Runtime Library, [http://msdn2.microsoft.com/en-us/library/abx4dbyh(vs.71).aspx Redistributable]   -viewer now using msvcr80.dll when compiled with VC2005&lt;br /&gt;
&lt;br /&gt;
== FONTS ==&lt;br /&gt;
Meta font software files&lt;br /&gt;
&lt;br /&gt;
From the Readme.txt&lt;br /&gt;
 The Meta font software files contained in this folder are the&lt;br /&gt;
 copyrighted property of FSI FontShop International (&amp;quot;FSI&amp;quot;) and are&lt;br /&gt;
 licensed by FSI solely for use by Linden Research, Inc. and by&lt;br /&gt;
 residents or users of Second Life in the Second Life environment,&lt;br /&gt;
 subject to the Second Life Terms of Service.  These Meta font software&lt;br /&gt;
 files may not be copied by residents or developers of Second Life or&lt;br /&gt;
 used by them for any other purpose whatsoever.&lt;br /&gt;
&lt;br /&gt;
*  MtBkLfRg.ttf &lt;br /&gt;
*  MtBdLfRg.ttf&lt;br /&gt;
&lt;br /&gt;
NOTE:  These files must be replaced for an true opensource build&lt;br /&gt;
&lt;br /&gt;
=== Kakadu ===&lt;br /&gt;
JPEG2000 library. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;.&lt;br /&gt;
OpenJPEG is an alternative library that is redistributable&lt;br /&gt;
&lt;br /&gt;
* llkdu.dll&lt;br /&gt;
&lt;br /&gt;
Starting with Viewer 2.5, llkdu.dll has been phased out. The libkdu.a and kdu.lib are still not redistributable but can be built from the toolkit provided by Kakadu under a license. How to build a viewer with such a library is documented in [[KDU Implementation]].&lt;br /&gt;
&lt;br /&gt;
=== OpenSSL ===&lt;br /&gt;
SSL library. Redistributable.&lt;br /&gt;
&lt;br /&gt;
* libeay32.dll&lt;br /&gt;
* ssleay32.dll&lt;br /&gt;
&lt;br /&gt;
=== Vivox ===&lt;br /&gt;
Vivox is the component that provides the voice functionality.&lt;br /&gt;
&lt;br /&gt;
* SLVoice.exe: Voice daemon. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* SLVoiceAgent: Exact purpose unknown. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* alut.dll: OpenAL, by Creative Labs. Redistributable.&lt;br /&gt;
* ortp.dll: oRTP project, LGPL. Redistributable.&lt;br /&gt;
* srtp.dll: Secure RTP?&lt;br /&gt;
* vivoxsdk.dll: Vivox Communications API. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* wrap_oal.dll: OpenAL32 by Creative Labs. Redistributable.&lt;br /&gt;
&lt;br /&gt;
=== Unknown Libraries ===&lt;br /&gt;
The purpose and licensing of these files is not known:&lt;br /&gt;
&lt;br /&gt;
* tntk.dll: Probably Vivox related  -see [https://wiki.secondlife.com/wiki/Voice/Technical Voice/Technical] for other info on ViVox files&lt;br /&gt;
&lt;br /&gt;
[[Category:Compiling viewer]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217099</id>
		<title>Third Party Libraries</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217099"/>
		<updated>2024-08-11T05:22:27Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
The following table lists libraries and other third party (3p) dependencies used in constructing the Second Life Viewer.&lt;br /&gt;
&lt;br /&gt;
Most of these (all the open source ones) should be available as prebuilds packaged by and for use with [[Autobuild]].&lt;br /&gt;
&lt;br /&gt;
==== Generic Layout (Single-branch, 2024 Style) ====&lt;br /&gt;
&lt;br /&gt;
3p packages should vendor upstream contents using [https://git-scm.com/book/en/v2/Git-Tools-Submodules git submodules] unless a source git repository for the source dependency is not available.&lt;br /&gt;
A typical 3p package layout looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
.&lt;br /&gt;
├── [vendor submodule]/ - Git submodule containing upstream source&lt;br /&gt;
├── .github/            - CI/CD and repository configuration&lt;br /&gt;
├── autobuild.xml       - Autobuild manifest&lt;br /&gt;
├── build-cmd.sh        - Script used to build the repository&lt;br /&gt;
├── LICENSE             - Repository licensing&lt;br /&gt;
└── README.md           - Repository readme&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Contributor Instructions =====&lt;br /&gt;
&lt;br /&gt;
The general workflow for contributing changes to a 3p library looks like this:&lt;br /&gt;
&lt;br /&gt;
# Fork the repository and make changes. Be sure to test your work locally.&lt;br /&gt;
# Create a PR (Be sure to follow [https://gist.github.com/mikepea/863f63d6e37281e329f8 PR Etiquette] and our [https://github.com/secondlife/.github/blob/main/CODE_OF_CONDUCT.md Code of Conduct])&lt;br /&gt;
# Have this PR reviewed and merged&lt;br /&gt;
# Done! The maintainer should cut a new version as appropriate using the workflow described below.&lt;br /&gt;
&lt;br /&gt;
===== Maintainer Instructions =====&lt;br /&gt;
&lt;br /&gt;
[[File:Screenshot 2024-08-10 at 11-48-23 Releases · secondlife 3p-zlib-ng.png|thumb|top|right|A properly versioned and promoted release of 3p-zlib-ng]]&lt;br /&gt;
&lt;br /&gt;
If you are a 3p package maintainer you will want to follow these instructions to review and release new versions:&lt;br /&gt;
&lt;br /&gt;
# Review PRs quickly, and merge them directly into the default branch once approved&lt;br /&gt;
# Cut a new release by using Github&#039;s Releases feature:&lt;br /&gt;
## Navigate to &#039;&#039;&#039;Repository -&amp;gt; Releases&#039;&#039;&#039; and click &#039;&#039;&#039;&amp;quot;Draft new Release&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
## Click &#039;&#039;&#039;Choose a tag&#039;&#039;&#039; and create a new tag using the &amp;lt;code&amp;gt;vUPSTREAM-rRELEASE&amp;lt;/code&amp;gt; format described below&lt;br /&gt;
## Click &#039;&#039;&#039;Generate release notes&#039;&#039;&#039; and edit them as appropriate. Keep the title of the release equal to the version being used.&lt;br /&gt;
## Click &#039;&#039;Publish release&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
After the release is published, CI/CD should be triggered by the addition of the new tag, and provide a nice set of packages and instructions for consumers to use.&lt;br /&gt;
&lt;br /&gt;
===== Version format =====&lt;br /&gt;
&lt;br /&gt;
3p packages should be released using the following version schema:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Format&lt;br /&gt;
&lt;br /&gt;
  vUPSTREAM-rRELEASE&lt;br /&gt;
&lt;br /&gt;
  Example: v1.0.0-r2 (Second vendored release of v1.0.0 of this library)&lt;br /&gt;
&lt;br /&gt;
  UPSTREAM - Upstream package version. ex. 1.0.0 or 6.7&lt;br /&gt;
  RELEASE - Vendored release number, ex. 1, 2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Patches =====&lt;br /&gt;
&lt;br /&gt;
By convention, patches should be kept in a &amp;lt;code&amp;gt;patches/*&amp;lt;/code&amp;gt; directory and applied to the upstream source during build. You can use the following utility function, declared in your &#039;&#039;&#039;build-cmd.sh&#039;&#039;&#039; script to help:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
apply_patch()&lt;br /&gt;
{&lt;br /&gt;
    local patch=&amp;quot;$1&amp;quot;&lt;br /&gt;
    local path=&amp;quot;$2&amp;quot;&lt;br /&gt;
    echo &amp;quot;Applying $patch...&amp;quot;&lt;br /&gt;
    git apply --check --reverse --directory=&amp;quot;$path&amp;quot; &amp;quot;$patch&amp;quot; || git apply --directory=&amp;quot;$path&amp;quot; &amp;quot;$patch&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&#039;&#039;Patch helper from [https://github.com/secondlife/3p-boost 3p-boost]&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
===== Differences from old style (vendor branch) =====&lt;br /&gt;
&lt;br /&gt;
Originally, 3p libraries were maintained using a separate &#039;&#039;&#039;vendor&#039;&#039;&#039; and default branch. Repositories using this style should be migrated to new single-branch format. Benefits of using a single-branch are:&lt;br /&gt;
&lt;br /&gt;
* Simpler PR process (Make PR against default, merge it. Done.)&lt;br /&gt;
* Faster release process, which means quicker integration for package consumers&lt;br /&gt;
* Easier integration with GitHub functionality such as Automatic Release Notes&lt;br /&gt;
* More explicit maintenance of patches makes delta from upstreams clearer and simpler to understand&lt;br /&gt;
&lt;br /&gt;
=== List of third party libraries ===&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;3&amp;quot;&lt;br /&gt;
|- style=&amp;quot;background:#ffdead;&amp;quot;&lt;br /&gt;
!Library&lt;br /&gt;
!Repository&lt;br /&gt;
!License&lt;br /&gt;
!Upstream Home&lt;br /&gt;
!Description&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|apr_suite&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/ 3p-apr]&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/blob/main/apr/LICENSE Apache License]&lt;br /&gt;
|http://apr.apache.org/&lt;br /&gt;
|&amp;quot;A set of C functions which provide a portable and consistent interface into operating system - or simply widely useful - functionality. We primarily use it for threading and socket i/o. This is actually three packages, apr, apr-util and apr-iconv.&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|boost&lt;br /&gt;
|[https://github.com/secondlife/3p-boost 3p-boost]&lt;br /&gt;
|[https://github.com/boostorg/boost/blob/master/LICENSE_1_0.txt Boost License]&lt;br /&gt;
|http://www.boost.org/&lt;br /&gt;
|A set of portable C++ libraries which provide a wide set of functionality. Used primarily for tokenization.&lt;br /&gt;
|-&lt;br /&gt;
|curl&lt;br /&gt;
|[https://github.com/secondlife/3p-curl 3p-curl]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-curl/blob/master/curl/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
| http://curl.haxx.se/libcurl/&lt;br /&gt;
|Handles moving data across the net in many different protocols. Used to GET/POST/PUT/DELETE web resources.&lt;br /&gt;
|-&lt;br /&gt;
|expat&lt;br /&gt;
|[https://github.com/secondlife/3p-expat 3p-expat]&lt;br /&gt;
|[https://github.com/libexpat/libexpat/blob/master/COPYING MIT License]&lt;br /&gt;
|https://github.com/libexpat/libexpat&lt;br /&gt;
|XML parser.&lt;br /&gt;
|-&lt;br /&gt;
|freetype&lt;br /&gt;
|[https://github.com/secondlife/3p-freetype 3p-freetype]&lt;br /&gt;
|[https://github.com/freetype/freetype/blob/master/LICENSE.TXT Freetype License and Others]&lt;br /&gt;
|http://www.freetype.org/&lt;br /&gt;
|Font engine.&lt;br /&gt;
|-&lt;br /&gt;
|glh_linear&lt;br /&gt;
|[https://github.com/secondlife/3p-glh_linear 3p-glh_linear]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|nVidia NVParse SDK: platform-independent C++ Apple OpenGL helper library&lt;br /&gt;
|-&lt;br /&gt;
|gstreamer&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-gstreamer 3p-gstreamer]&lt;br /&gt;
|Open&lt;br /&gt;
|http://gstreamer.freedesktop.org&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|havok&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|jpeglib&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-jpeglib 3p-jpeglib]&lt;br /&gt;
|Open&lt;br /&gt;
|http://www.ijg.org/&lt;br /&gt;
|JPEG decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|kdu&lt;br /&gt;
|[https://hg.lindenlab.com/lindenlab/3p-kdu-private 3p-kdu-private]&amp;lt;sup&amp;gt;[[#PrivateRepo|LL]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|Commercial&lt;br /&gt;
|http://www.kakadusoftware.com&lt;br /&gt;
|Kakadu (KDU) JPEG-2000 decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|libpng&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-libpng 3p-libpng]&lt;br /&gt;
|Open&lt;br /&gt;
| http://www.libpng.org/pub/png/libpng.html&lt;br /&gt;
|PNG image library.&lt;br /&gt;
|-&lt;br /&gt;
|libxml2&lt;br /&gt;
|[https://github.com/secondlife/3p-libxml2 3p-libxml2]&lt;br /&gt;
|[https://gitlab.gnome.org/GNOME/libxml2/-/blob/master/Copyright?ref_type=heads MIT License]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|ndofdev&lt;br /&gt;
|[https://github.com/secondlife/3p-libndofdev 3p-libndofdev]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-libndofdev/blob/master/libndofdev/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|Provides joystick driver support (mac and win) for the 3DConnexion SpaceNavigator.&lt;br /&gt;
|-&lt;br /&gt;
|ogg_vorbis&lt;br /&gt;
|[https://github.com/secondlife/3p-ogg_vorbis 3p-ogvorbis]&lt;br /&gt;
|&#039;&#039;[https://github.com/xiph/ogg/blob/master/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://xiph.org/ogg/&lt;br /&gt;
|Audio codec and playback.&lt;br /&gt;
|-&lt;br /&gt;
|openal&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft 3p-openal-soft]&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft/blob/main/openal-soft/COPYING GPL]&lt;br /&gt;
|http://connect.creativelabs.com/openal/default.aspx&lt;br /&gt;
|3D Spatial audio&lt;br /&gt;
|-&lt;br /&gt;
|openjpeg&lt;br /&gt;
|[https://github.com/secondlife/3p-openjpeg 3p-openjpeg]&lt;br /&gt;
|&#039;&#039;[https://github.com/uclouvain/openjpeg/blob/master/LICENSE BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://www.openjpeg.org/&lt;br /&gt;
|An open-source JPEG-2000 library; a slower alternative to Kadaku. Used in the open source release&lt;br /&gt;
|-&lt;br /&gt;
|openssl&lt;br /&gt;
|[https://github.com/secondlife/3p-openssl 3p-openssl]&lt;br /&gt;
|[https://github.com/openssl/openssl/blob/master/LICENSE.txt Apache License]&lt;br /&gt;
|http://www.openssl.org/&lt;br /&gt;
|Provides encryption for sensitive actions such as user login&lt;br /&gt;
|-&lt;br /&gt;
|sdl&lt;br /&gt;
|[https://github.com/secondlife/3p-sdl2 3p-sdl2]&lt;br /&gt;
|[https://github.com/libsdl-org/SDL/blob/main/LICENSE.txt zlib License]&lt;br /&gt;
|http://www.libsdl.org/index.php&lt;br /&gt;
|The Simple DirectMedia Layer libraries are used for handling input and basic window/GL setup on the Linux client.&lt;br /&gt;
|-&lt;br /&gt;
|slvoice&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|This is the Vivox provided executable (renamed SLVoice)&lt;br /&gt;
|-&lt;br /&gt;
|tut&lt;br /&gt;
|[https://github.com/secondlife/3p-tut 3p-tut]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-tut/blob/master/LICENSE.txt BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://tut-framework.sourceforge.net/&lt;br /&gt;
|Unit test framework based on the use of c++ meta template programming. Used to test LL libraries.&lt;br /&gt;
|-&lt;br /&gt;
|xmlrpc_epi&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi 3p-xmlrpc-epi]&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi/blob/master/xmlrpc-epi/COPYING Epinions, Inc License]&lt;br /&gt;
|http://xmlrpc-epi.sourceforge.net/&lt;br /&gt;
|XML-RPC protocol utility&lt;br /&gt;
|-&lt;br /&gt;
|zlib-ng&lt;br /&gt;
|[https://github.com/secondlife/3p-zlib-ng 3p-zlib-ng]&lt;br /&gt;
|[https://github.com/zlib-ng/zlib-ng/blob/develop/LICENSE.md zlib License]&lt;br /&gt;
|http://www.zlib.net/&lt;br /&gt;
|zlib is a lossless data-compression library which is used for a variety of network i/o and file compression.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Redistribution =&lt;br /&gt;
Some files in the SL installation can&#039;t be freely redistributed. This means it&#039;s not possible to simply package and offer for download a modified viewer: some files &#039;&#039;&#039;must not&#039;&#039;&#039; be redistributed. &lt;br /&gt;
&lt;br /&gt;
This page documents the licenses and redistribution requirements of various files used by the viewer.&lt;br /&gt;
&lt;br /&gt;
== Windows ==&lt;br /&gt;
These are the files that need to be shipped with the Windows version&lt;br /&gt;
&lt;br /&gt;
=== Runtime ===&lt;br /&gt;
Microsoft components. It&#039;s not yet known whether they&#039;re redistributable.&lt;br /&gt;
&lt;br /&gt;
* dbghelp.dll: [http://msdn2.microsoft.com/EN-US/library/ms679294.aspx Redistributable]&lt;br /&gt;
* msvcp71.dll: C++ Runtime Library, redistributable    -viewer now using msvcp80.dll when compiled with VC2005&lt;br /&gt;
* msvcr71.dll: C Runtime Library, [http://msdn2.microsoft.com/en-us/library/abx4dbyh(vs.71).aspx Redistributable]   -viewer now using msvcr80.dll when compiled with VC2005&lt;br /&gt;
&lt;br /&gt;
== FONTS ==&lt;br /&gt;
Meta font software files&lt;br /&gt;
&lt;br /&gt;
From the Readme.txt&lt;br /&gt;
 The Meta font software files contained in this folder are the&lt;br /&gt;
 copyrighted property of FSI FontShop International (&amp;quot;FSI&amp;quot;) and are&lt;br /&gt;
 licensed by FSI solely for use by Linden Research, Inc. and by&lt;br /&gt;
 residents or users of Second Life in the Second Life environment,&lt;br /&gt;
 subject to the Second Life Terms of Service.  These Meta font software&lt;br /&gt;
 files may not be copied by residents or developers of Second Life or&lt;br /&gt;
 used by them for any other purpose whatsoever.&lt;br /&gt;
&lt;br /&gt;
*  MtBkLfRg.ttf &lt;br /&gt;
*  MtBdLfRg.ttf&lt;br /&gt;
&lt;br /&gt;
NOTE:  These files must be replaced for an true opensource build&lt;br /&gt;
&lt;br /&gt;
=== Mozilla ===&lt;br /&gt;
The Gecko engine is MPL/GPL/LGPL licensed, which makes it redistributable.&lt;br /&gt;
&lt;br /&gt;
* app_settings/mozilla&lt;br /&gt;
* freebl3.dll&lt;br /&gt;
* gksvggdiplus.dll&lt;br /&gt;
* js3250.dll&lt;br /&gt;
* nspr4.dll&lt;br /&gt;
* nss3.dll&lt;br /&gt;
* nssckbi.dll&lt;br /&gt;
* plc4.dll,&lt;br /&gt;
* plds4.dll&lt;br /&gt;
* smime3.dll&lt;br /&gt;
* softokn3.dll&lt;br /&gt;
* ssl3.dll&lt;br /&gt;
* xpcom.dll&lt;br /&gt;
* xul.dll&lt;br /&gt;
&lt;br /&gt;
=== Kakadu ===&lt;br /&gt;
JPEG2000 library. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;.&lt;br /&gt;
OpenJPEG is an alternative library that is redistributable&lt;br /&gt;
&lt;br /&gt;
* llkdu.dll&lt;br /&gt;
&lt;br /&gt;
Starting with Viewer 2.5, llkdu.dll has been phased out. The libkdu.a and kdu.lib are still not redistributable but can be built from the toolkit provided by Kakadu under a license. How to build a viewer with such a library is documented in [[KDU Implementation]].&lt;br /&gt;
&lt;br /&gt;
=== OpenSSL ===&lt;br /&gt;
SSL library. Redistributable.&lt;br /&gt;
&lt;br /&gt;
* libeay32.dll&lt;br /&gt;
* ssleay32.dll&lt;br /&gt;
&lt;br /&gt;
=== Vivox ===&lt;br /&gt;
Vivox is the component that provides the voice functionality.&lt;br /&gt;
&lt;br /&gt;
* SLVoice.exe: Voice daemon. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* SLVoiceAgent: Exact purpose unknown. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* alut.dll: OpenAL, by Creative Labs. Redistributable.&lt;br /&gt;
* ortp.dll: oRTP project, LGPL. Redistributable.&lt;br /&gt;
* srtp.dll: Secure RTP?&lt;br /&gt;
* vivoxsdk.dll: Vivox Communications API. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* wrap_oal.dll: OpenAL32 by Creative Labs. Redistributable.&lt;br /&gt;
&lt;br /&gt;
=== Unknown Libraries ===&lt;br /&gt;
The purpose and licensing of these files is not known:&lt;br /&gt;
&lt;br /&gt;
* tntk.dll: Probably Vivox related  -see [https://wiki.secondlife.com/wiki/Voice/Technical Voice/Technical] for other info on ViVox files&lt;br /&gt;
&lt;br /&gt;
[[Category:Compiling viewer]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217098</id>
		<title>Third Party Libraries</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217098"/>
		<updated>2024-08-11T05:21:53Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
The following table lists libraries and other third party (3p) dependencies used in constructing the Second Life Viewer.&lt;br /&gt;
&lt;br /&gt;
Most of these (all the open source ones) should be available as prebuilds packaged by and for use with [[Autobuild]].&lt;br /&gt;
&lt;br /&gt;
==== Generic Layout (Single-branch, 2024 Style) ====&lt;br /&gt;
&lt;br /&gt;
3p packages should vendor upstream contents using [https://git-scm.com/book/en/v2/Git-Tools-Submodules git submodules] unless a source git repository for the source dependency is not available.&lt;br /&gt;
A typical 3p package layout looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
.&lt;br /&gt;
├── [vendor submodule]/ - Git submodule containing upstream source&lt;br /&gt;
├── .github/            - CI/CD and repository configuration&lt;br /&gt;
├── autobuild.xml       - Autobuild manifest&lt;br /&gt;
├── build-cmd.sh        - Script used to build the repository&lt;br /&gt;
├── LICENSE             - Repository licensing&lt;br /&gt;
└── README.md           - Repository readme&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Contributor Instructions =====&lt;br /&gt;
&lt;br /&gt;
The general workflow for contributing changes to a 3p library looks like this:&lt;br /&gt;
&lt;br /&gt;
# Fork the repository and make changes. Be sure to test your work locally.&lt;br /&gt;
# Create a PR (Be sure to follow [https://gist.github.com/mikepea/863f63d6e37281e329f8 PR Etiquette] and our [https://github.com/secondlife/.github/blob/main/CODE_OF_CONDUCT.md Code of Conduct])&lt;br /&gt;
# Have this PR reviewed and merged&lt;br /&gt;
# Done! The maintainer should cut a new version as appropriate using the workflow described below.&lt;br /&gt;
&lt;br /&gt;
===== Maintainer Instructions =====&lt;br /&gt;
&lt;br /&gt;
[[File:Screenshot 2024-08-10 at 11-48-23 Releases · secondlife 3p-zlib-ng.png|thumb|top|right|A properly versioned and promoted release of 3p-zlib-ng]]&lt;br /&gt;
&lt;br /&gt;
If you are a 3p package maintainer you will want to follow these instructions to review and release new versions:&lt;br /&gt;
&lt;br /&gt;
# Review PRs quickly, and merge them directly into the default branch once approved&lt;br /&gt;
# Cut a new release by using Github&#039;s Releases feature:&lt;br /&gt;
## Navigate to &#039;&#039;&#039;Repository -&amp;gt; Releases&#039;&#039;&#039; and click &#039;&#039;&#039;&amp;quot;Draft new Release&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
## Click &#039;&#039;&#039;Choose a tag&#039;&#039;&#039; and create a new tag using the &amp;lt;code&amp;gt;vUPSTREAM-rRELEASE&amp;lt;/code&amp;gt; format described below&lt;br /&gt;
## Click &#039;&#039;&#039;Generate release notes&#039;&#039;&#039; and edit them as appropriate. Keep the title of the release equal to the version being used.&lt;br /&gt;
## Click &#039;&#039;Publish release&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
After the release is published, CI/CD should be triggered by the addition of the new tag, and provide a nice set of packages and instructions for consumers to use.&lt;br /&gt;
&lt;br /&gt;
===== Version format =====&lt;br /&gt;
&lt;br /&gt;
3p packages should be released using the following version schema:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Format&lt;br /&gt;
&lt;br /&gt;
  vUPSTREAM-rRELEASE&lt;br /&gt;
&lt;br /&gt;
  Example: v1.0.0-r2 (Second vendored release of v1.0.0 of this library)&lt;br /&gt;
&lt;br /&gt;
  UPSTREAM - Upstream package version. ex. 1.0.0 or 6.7&lt;br /&gt;
  RELEASE - Vendored release number, ex. 1, 2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Patches =====&lt;br /&gt;
&lt;br /&gt;
By convention, patches should be kept in a &amp;lt;code&amp;gt;patches/*&amp;lt;/code&amp;gt; directory and applied to the upstream source during build. You can use the following utility function, declared in your &#039;&#039;&#039;build-cmd.sh&#039;&#039;&#039; script to help:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
apply_patch()&lt;br /&gt;
{&lt;br /&gt;
    local patch=&amp;quot;$1&amp;quot;&lt;br /&gt;
    local path=&amp;quot;$2&amp;quot;&lt;br /&gt;
    echo &amp;quot;Applying $patch...&amp;quot;&lt;br /&gt;
    git apply --check --reverse --directory=&amp;quot;$path&amp;quot; &amp;quot;$patch&amp;quot; || git apply --directory=&amp;quot;$path&amp;quot; &amp;quot;$patch&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&#039;&#039;Patch helper from [https://github.com/secondlife/3p-boost 3p-boost]&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
===== Differences from old style (vendor branch) =====&lt;br /&gt;
&lt;br /&gt;
Originally, 3p libraries were maintained using a separate `vendor` and default branch. Repositories using this style should be migrated to new single-branch format. Benefits of using a single-branch are:&lt;br /&gt;
&lt;br /&gt;
* Simpler PR process (Make PR against default, merge it. Done.)&lt;br /&gt;
* Faster release process, which means quicker integration for package consumers&lt;br /&gt;
* Easier integration with GitHub functionality such as Automatic Release Notes&lt;br /&gt;
* More explicit maintenance of patches makes delta from upstreams clearer and simpler to understand&lt;br /&gt;
&lt;br /&gt;
=== List of third party libraries ===&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;3&amp;quot;&lt;br /&gt;
|- style=&amp;quot;background:#ffdead;&amp;quot;&lt;br /&gt;
!Library&lt;br /&gt;
!Repository&lt;br /&gt;
!License&lt;br /&gt;
!Upstream Home&lt;br /&gt;
!Description&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|apr_suite&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/ 3p-apr]&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/blob/main/apr/LICENSE Apache License]&lt;br /&gt;
|http://apr.apache.org/&lt;br /&gt;
|&amp;quot;A set of C functions which provide a portable and consistent interface into operating system - or simply widely useful - functionality. We primarily use it for threading and socket i/o. This is actually three packages, apr, apr-util and apr-iconv.&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|boost&lt;br /&gt;
|[https://github.com/secondlife/3p-boost 3p-boost]&lt;br /&gt;
|[https://github.com/boostorg/boost/blob/master/LICENSE_1_0.txt Boost License]&lt;br /&gt;
|http://www.boost.org/&lt;br /&gt;
|A set of portable C++ libraries which provide a wide set of functionality. Used primarily for tokenization.&lt;br /&gt;
|-&lt;br /&gt;
|curl&lt;br /&gt;
|[https://github.com/secondlife/3p-curl 3p-curl]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-curl/blob/master/curl/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
| http://curl.haxx.se/libcurl/&lt;br /&gt;
|Handles moving data across the net in many different protocols. Used to GET/POST/PUT/DELETE web resources.&lt;br /&gt;
|-&lt;br /&gt;
|expat&lt;br /&gt;
|[https://github.com/secondlife/3p-expat 3p-expat]&lt;br /&gt;
|[https://github.com/libexpat/libexpat/blob/master/COPYING MIT License]&lt;br /&gt;
|https://github.com/libexpat/libexpat&lt;br /&gt;
|XML parser.&lt;br /&gt;
|-&lt;br /&gt;
|freetype&lt;br /&gt;
|[https://github.com/secondlife/3p-freetype 3p-freetype]&lt;br /&gt;
|[https://github.com/freetype/freetype/blob/master/LICENSE.TXT Freetype License and Others]&lt;br /&gt;
|http://www.freetype.org/&lt;br /&gt;
|Font engine.&lt;br /&gt;
|-&lt;br /&gt;
|glh_linear&lt;br /&gt;
|[https://github.com/secondlife/3p-glh_linear 3p-glh_linear]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|nVidia NVParse SDK: platform-independent C++ Apple OpenGL helper library&lt;br /&gt;
|-&lt;br /&gt;
|gstreamer&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-gstreamer 3p-gstreamer]&lt;br /&gt;
|Open&lt;br /&gt;
|http://gstreamer.freedesktop.org&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|havok&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|jpeglib&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-jpeglib 3p-jpeglib]&lt;br /&gt;
|Open&lt;br /&gt;
|http://www.ijg.org/&lt;br /&gt;
|JPEG decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|kdu&lt;br /&gt;
|[https://hg.lindenlab.com/lindenlab/3p-kdu-private 3p-kdu-private]&amp;lt;sup&amp;gt;[[#PrivateRepo|LL]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|Commercial&lt;br /&gt;
|http://www.kakadusoftware.com&lt;br /&gt;
|Kakadu (KDU) JPEG-2000 decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|libpng&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-libpng 3p-libpng]&lt;br /&gt;
|Open&lt;br /&gt;
| http://www.libpng.org/pub/png/libpng.html&lt;br /&gt;
|PNG image library.&lt;br /&gt;
|-&lt;br /&gt;
|libxml2&lt;br /&gt;
|[https://github.com/secondlife/3p-libxml2 3p-libxml2]&lt;br /&gt;
|[https://gitlab.gnome.org/GNOME/libxml2/-/blob/master/Copyright?ref_type=heads MIT License]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|ndofdev&lt;br /&gt;
|[https://github.com/secondlife/3p-libndofdev 3p-libndofdev]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-libndofdev/blob/master/libndofdev/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|Provides joystick driver support (mac and win) for the 3DConnexion SpaceNavigator.&lt;br /&gt;
|-&lt;br /&gt;
|ogg_vorbis&lt;br /&gt;
|[https://github.com/secondlife/3p-ogg_vorbis 3p-ogvorbis]&lt;br /&gt;
|&#039;&#039;[https://github.com/xiph/ogg/blob/master/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://xiph.org/ogg/&lt;br /&gt;
|Audio codec and playback.&lt;br /&gt;
|-&lt;br /&gt;
|openal&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft 3p-openal-soft]&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft/blob/main/openal-soft/COPYING GPL]&lt;br /&gt;
|http://connect.creativelabs.com/openal/default.aspx&lt;br /&gt;
|3D Spatial audio&lt;br /&gt;
|-&lt;br /&gt;
|openjpeg&lt;br /&gt;
|[https://github.com/secondlife/3p-openjpeg 3p-openjpeg]&lt;br /&gt;
|&#039;&#039;[https://github.com/uclouvain/openjpeg/blob/master/LICENSE BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://www.openjpeg.org/&lt;br /&gt;
|An open-source JPEG-2000 library; a slower alternative to Kadaku. Used in the open source release&lt;br /&gt;
|-&lt;br /&gt;
|openssl&lt;br /&gt;
|[https://github.com/secondlife/3p-openssl 3p-openssl]&lt;br /&gt;
|[https://github.com/openssl/openssl/blob/master/LICENSE.txt Apache License]&lt;br /&gt;
|http://www.openssl.org/&lt;br /&gt;
|Provides encryption for sensitive actions such as user login&lt;br /&gt;
|-&lt;br /&gt;
|sdl&lt;br /&gt;
|[https://github.com/secondlife/3p-sdl2 3p-sdl2]&lt;br /&gt;
|[https://github.com/libsdl-org/SDL/blob/main/LICENSE.txt zlib License]&lt;br /&gt;
|http://www.libsdl.org/index.php&lt;br /&gt;
|The Simple DirectMedia Layer libraries are used for handling input and basic window/GL setup on the Linux client.&lt;br /&gt;
|-&lt;br /&gt;
|slvoice&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|This is the Vivox provided executable (renamed SLVoice)&lt;br /&gt;
|-&lt;br /&gt;
|tut&lt;br /&gt;
|[https://github.com/secondlife/3p-tut 3p-tut]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-tut/blob/master/LICENSE.txt BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://tut-framework.sourceforge.net/&lt;br /&gt;
|Unit test framework based on the use of c++ meta template programming. Used to test LL libraries.&lt;br /&gt;
|-&lt;br /&gt;
|xmlrpc_epi&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi 3p-xmlrpc-epi]&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi/blob/master/xmlrpc-epi/COPYING Epinions, Inc License]&lt;br /&gt;
|http://xmlrpc-epi.sourceforge.net/&lt;br /&gt;
|XML-RPC protocol utility&lt;br /&gt;
|-&lt;br /&gt;
|zlib-ng&lt;br /&gt;
|[https://github.com/secondlife/3p-zlib-ng 3p-zlib-ng]&lt;br /&gt;
|[https://github.com/zlib-ng/zlib-ng/blob/develop/LICENSE.md zlib License]&lt;br /&gt;
|http://www.zlib.net/&lt;br /&gt;
|zlib is a lossless data-compression library which is used for a variety of network i/o and file compression.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Redistribution =&lt;br /&gt;
Some files in the SL installation can&#039;t be freely redistributed. This means it&#039;s not possible to simply package and offer for download a modified viewer: some files &#039;&#039;&#039;must not&#039;&#039;&#039; be redistributed. &lt;br /&gt;
&lt;br /&gt;
This page documents the licenses and redistribution requirements of various files used by the viewer.&lt;br /&gt;
&lt;br /&gt;
== Windows ==&lt;br /&gt;
These are the files that need to be shipped with the Windows version&lt;br /&gt;
&lt;br /&gt;
=== Runtime ===&lt;br /&gt;
Microsoft components. It&#039;s not yet known whether they&#039;re redistributable.&lt;br /&gt;
&lt;br /&gt;
* dbghelp.dll: [http://msdn2.microsoft.com/EN-US/library/ms679294.aspx Redistributable]&lt;br /&gt;
* msvcp71.dll: C++ Runtime Library, redistributable    -viewer now using msvcp80.dll when compiled with VC2005&lt;br /&gt;
* msvcr71.dll: C Runtime Library, [http://msdn2.microsoft.com/en-us/library/abx4dbyh(vs.71).aspx Redistributable]   -viewer now using msvcr80.dll when compiled with VC2005&lt;br /&gt;
&lt;br /&gt;
== FONTS ==&lt;br /&gt;
Meta font software files&lt;br /&gt;
&lt;br /&gt;
From the Readme.txt&lt;br /&gt;
 The Meta font software files contained in this folder are the&lt;br /&gt;
 copyrighted property of FSI FontShop International (&amp;quot;FSI&amp;quot;) and are&lt;br /&gt;
 licensed by FSI solely for use by Linden Research, Inc. and by&lt;br /&gt;
 residents or users of Second Life in the Second Life environment,&lt;br /&gt;
 subject to the Second Life Terms of Service.  These Meta font software&lt;br /&gt;
 files may not be copied by residents or developers of Second Life or&lt;br /&gt;
 used by them for any other purpose whatsoever.&lt;br /&gt;
&lt;br /&gt;
*  MtBkLfRg.ttf &lt;br /&gt;
*  MtBdLfRg.ttf&lt;br /&gt;
&lt;br /&gt;
NOTE:  These files must be replaced for an true opensource build&lt;br /&gt;
&lt;br /&gt;
=== Mozilla ===&lt;br /&gt;
The Gecko engine is MPL/GPL/LGPL licensed, which makes it redistributable.&lt;br /&gt;
&lt;br /&gt;
* app_settings/mozilla&lt;br /&gt;
* freebl3.dll&lt;br /&gt;
* gksvggdiplus.dll&lt;br /&gt;
* js3250.dll&lt;br /&gt;
* nspr4.dll&lt;br /&gt;
* nss3.dll&lt;br /&gt;
* nssckbi.dll&lt;br /&gt;
* plc4.dll,&lt;br /&gt;
* plds4.dll&lt;br /&gt;
* smime3.dll&lt;br /&gt;
* softokn3.dll&lt;br /&gt;
* ssl3.dll&lt;br /&gt;
* xpcom.dll&lt;br /&gt;
* xul.dll&lt;br /&gt;
&lt;br /&gt;
=== Kakadu ===&lt;br /&gt;
JPEG2000 library. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;.&lt;br /&gt;
OpenJPEG is an alternative library that is redistributable&lt;br /&gt;
&lt;br /&gt;
* llkdu.dll&lt;br /&gt;
&lt;br /&gt;
Starting with Viewer 2.5, llkdu.dll has been phased out. The libkdu.a and kdu.lib are still not redistributable but can be built from the toolkit provided by Kakadu under a license. How to build a viewer with such a library is documented in [[KDU Implementation]].&lt;br /&gt;
&lt;br /&gt;
=== OpenSSL ===&lt;br /&gt;
SSL library. Redistributable.&lt;br /&gt;
&lt;br /&gt;
* libeay32.dll&lt;br /&gt;
* ssleay32.dll&lt;br /&gt;
&lt;br /&gt;
=== Vivox ===&lt;br /&gt;
Vivox is the component that provides the voice functionality.&lt;br /&gt;
&lt;br /&gt;
* SLVoice.exe: Voice daemon. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* SLVoiceAgent: Exact purpose unknown. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* alut.dll: OpenAL, by Creative Labs. Redistributable.&lt;br /&gt;
* ortp.dll: oRTP project, LGPL. Redistributable.&lt;br /&gt;
* srtp.dll: Secure RTP?&lt;br /&gt;
* vivoxsdk.dll: Vivox Communications API. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* wrap_oal.dll: OpenAL32 by Creative Labs. Redistributable.&lt;br /&gt;
&lt;br /&gt;
=== Unknown Libraries ===&lt;br /&gt;
The purpose and licensing of these files is not known:&lt;br /&gt;
&lt;br /&gt;
* tntk.dll: Probably Vivox related  -see [https://wiki.secondlife.com/wiki/Voice/Technical Voice/Technical] for other info on ViVox files&lt;br /&gt;
&lt;br /&gt;
[[Category:Compiling viewer]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217097</id>
		<title>Third Party Libraries</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217097"/>
		<updated>2024-08-11T05:21:09Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
The following table lists libraries and other third party (3p) dependencies used in constructing the Second Life Viewer.&lt;br /&gt;
&lt;br /&gt;
Most of these (all the open source ones) should be available as prebuilds packaged by and for use with [[Autobuild]].&lt;br /&gt;
&lt;br /&gt;
==== Generic Layout (Single-branch, 2024 Style) ====&lt;br /&gt;
&lt;br /&gt;
3p packages should vendor upstream contents using [https://git-scm.com/book/en/v2/Git-Tools-Submodules git submodules] unless a source git repository for the source dependency is not available.&lt;br /&gt;
A typical 3p package layout looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
.&lt;br /&gt;
├── [vendor submodule]/ - Git submodule containing upstream source&lt;br /&gt;
├── .github/            - CI/CD and repository configuration&lt;br /&gt;
├── autobuild.xml       - Autobuild manifest&lt;br /&gt;
├── build-cmd.sh        - Script used to build the repository&lt;br /&gt;
├── LICENSE             - Repository licensing&lt;br /&gt;
└── README.md           - Repository readme&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Contributor Instructions =====&lt;br /&gt;
&lt;br /&gt;
The general workflow for contributing changes to a 3p library looks like this:&lt;br /&gt;
&lt;br /&gt;
# Fork the repository and make changes. Be sure to test your work locally.&lt;br /&gt;
# Create a PR (Be sure to follow [https://gist.github.com/mikepea/863f63d6e37281e329f8 PR Etiquette] and our [https://github.com/secondlife/.github/blob/main/CODE_OF_CONDUCT.md Code of Conduct])&lt;br /&gt;
# Have this PR reviewed and merged&lt;br /&gt;
# Done! The maintainer should cut a new version as appropriate using the workflow described below.&lt;br /&gt;
&lt;br /&gt;
===== Maintainer Instructions =====&lt;br /&gt;
&lt;br /&gt;
[[File:Screenshot 2024-08-10 at 11-48-23 Releases · secondlife 3p-zlib-ng.png|thumb|top|right|A properly versioned and promoted release of 3p-zlib-ng]]&lt;br /&gt;
&lt;br /&gt;
If you are a 3p package maintainer you will want to follow these instructions to review and release new versions:&lt;br /&gt;
&lt;br /&gt;
# Review PRs quickly, and merge them directly into the default branch once approved&lt;br /&gt;
# Cut a new release by using Github&#039;s Releases feature:&lt;br /&gt;
## Navigate to &#039;&#039;&#039;Repository -&amp;gt; Releases&#039;&#039;&#039; and click &#039;&#039;&#039;&amp;quot;Draft new Release&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
## Click &#039;&#039;&#039;Choose a tag&#039;&#039;&#039; and create a new tag using the &amp;lt;code&amp;gt;vUPSTREAM-rRELEASE&amp;lt;/code&amp;gt; format described below&lt;br /&gt;
## Click &#039;&#039;&#039;Generate release notes&#039;&#039;&#039; and edit them as appropriate. Keep the title of the release equal to the version being used.&lt;br /&gt;
## Click &#039;&#039;Publish release&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
After the release is published, CI/CD should be triggered by the addition of the new tag, and provide a nice set of packages and instructions for consumers to use.&lt;br /&gt;
&lt;br /&gt;
===== Version format =====&lt;br /&gt;
&lt;br /&gt;
3p packages should be released using the following version schema:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Format&lt;br /&gt;
&lt;br /&gt;
  vUPSTREAM-rRELEASE&lt;br /&gt;
&lt;br /&gt;
  Example: v1.0.0-r2 (Second vendored release of v1.0.0 of this library)&lt;br /&gt;
&lt;br /&gt;
  UPSTREAM - Upstream package version. ex. 1.0.0 or 6.7&lt;br /&gt;
  RELEASE - Vendored release number, ex. 1, 2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Patches =====&lt;br /&gt;
&lt;br /&gt;
By convention, patches should be kept in a &amp;lt;code&amp;gt;patches/*&amp;lt;/code&amp;gt; directory and applied to the upstream source during build. You can use the following utility function, declared in your &#039;&#039;&#039;build-cmd.sh&#039;&#039;&#039; script to help:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
apply_patch()&lt;br /&gt;
{&lt;br /&gt;
    local patch=&amp;quot;$1&amp;quot;&lt;br /&gt;
    local path=&amp;quot;$2&amp;quot;&lt;br /&gt;
    echo &amp;quot;Applying $patch...&amp;quot;&lt;br /&gt;
    git apply --check --reverse --directory=&amp;quot;$path&amp;quot; &amp;quot;$patch&amp;quot; || git apply --directory=&amp;quot;$path&amp;quot; &amp;quot;$patch&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&#039;&#039;Patch helper from [https://github.com/secondlife/3p-boost 3p-boost]&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
===== Differences from old style (vendor branch) =====&lt;br /&gt;
&lt;br /&gt;
Originally, 3p libraries were maintained using a separate `vendor` and default branch. Repositories using this style should be migrated to new single-branch format. Benefits of using a single-branch are:&lt;br /&gt;
&lt;br /&gt;
- Simpler PR process (Make PR against default, merge it. Done.)&lt;br /&gt;
- Faster release process, which means quicker integration for package consumers&lt;br /&gt;
- Easier integration with GitHub functionality such as Automatic Release Notes&lt;br /&gt;
&lt;br /&gt;
=== List of third party libraries ===&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;3&amp;quot;&lt;br /&gt;
|- style=&amp;quot;background:#ffdead;&amp;quot;&lt;br /&gt;
!Library&lt;br /&gt;
!Repository&lt;br /&gt;
!License&lt;br /&gt;
!Upstream Home&lt;br /&gt;
!Description&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|apr_suite&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/ 3p-apr]&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/blob/main/apr/LICENSE Apache License]&lt;br /&gt;
|http://apr.apache.org/&lt;br /&gt;
|&amp;quot;A set of C functions which provide a portable and consistent interface into operating system - or simply widely useful - functionality. We primarily use it for threading and socket i/o. This is actually three packages, apr, apr-util and apr-iconv.&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|boost&lt;br /&gt;
|[https://github.com/secondlife/3p-boost 3p-boost]&lt;br /&gt;
|[https://github.com/boostorg/boost/blob/master/LICENSE_1_0.txt Boost License]&lt;br /&gt;
|http://www.boost.org/&lt;br /&gt;
|A set of portable C++ libraries which provide a wide set of functionality. Used primarily for tokenization.&lt;br /&gt;
|-&lt;br /&gt;
|curl&lt;br /&gt;
|[https://github.com/secondlife/3p-curl 3p-curl]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-curl/blob/master/curl/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
| http://curl.haxx.se/libcurl/&lt;br /&gt;
|Handles moving data across the net in many different protocols. Used to GET/POST/PUT/DELETE web resources.&lt;br /&gt;
|-&lt;br /&gt;
|expat&lt;br /&gt;
|[https://github.com/secondlife/3p-expat 3p-expat]&lt;br /&gt;
|[https://github.com/libexpat/libexpat/blob/master/COPYING MIT License]&lt;br /&gt;
|https://github.com/libexpat/libexpat&lt;br /&gt;
|XML parser.&lt;br /&gt;
|-&lt;br /&gt;
|freetype&lt;br /&gt;
|[https://github.com/secondlife/3p-freetype 3p-freetype]&lt;br /&gt;
|[https://github.com/freetype/freetype/blob/master/LICENSE.TXT Freetype License and Others]&lt;br /&gt;
|http://www.freetype.org/&lt;br /&gt;
|Font engine.&lt;br /&gt;
|-&lt;br /&gt;
|glh_linear&lt;br /&gt;
|[https://github.com/secondlife/3p-glh_linear 3p-glh_linear]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|nVidia NVParse SDK: platform-independent C++ Apple OpenGL helper library&lt;br /&gt;
|-&lt;br /&gt;
|gstreamer&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-gstreamer 3p-gstreamer]&lt;br /&gt;
|Open&lt;br /&gt;
|http://gstreamer.freedesktop.org&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|havok&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|jpeglib&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-jpeglib 3p-jpeglib]&lt;br /&gt;
|Open&lt;br /&gt;
|http://www.ijg.org/&lt;br /&gt;
|JPEG decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|kdu&lt;br /&gt;
|[https://hg.lindenlab.com/lindenlab/3p-kdu-private 3p-kdu-private]&amp;lt;sup&amp;gt;[[#PrivateRepo|LL]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|Commercial&lt;br /&gt;
|http://www.kakadusoftware.com&lt;br /&gt;
|Kakadu (KDU) JPEG-2000 decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|libpng&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-libpng 3p-libpng]&lt;br /&gt;
|Open&lt;br /&gt;
| http://www.libpng.org/pub/png/libpng.html&lt;br /&gt;
|PNG image library.&lt;br /&gt;
|-&lt;br /&gt;
|libxml2&lt;br /&gt;
|[https://github.com/secondlife/3p-libxml2 3p-libxml2]&lt;br /&gt;
|[https://gitlab.gnome.org/GNOME/libxml2/-/blob/master/Copyright?ref_type=heads MIT License]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|ndofdev&lt;br /&gt;
|[https://github.com/secondlife/3p-libndofdev 3p-libndofdev]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-libndofdev/blob/master/libndofdev/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|Provides joystick driver support (mac and win) for the 3DConnexion SpaceNavigator.&lt;br /&gt;
|-&lt;br /&gt;
|ogg_vorbis&lt;br /&gt;
|[https://github.com/secondlife/3p-ogg_vorbis 3p-ogvorbis]&lt;br /&gt;
|&#039;&#039;[https://github.com/xiph/ogg/blob/master/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://xiph.org/ogg/&lt;br /&gt;
|Audio codec and playback.&lt;br /&gt;
|-&lt;br /&gt;
|openal&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft 3p-openal-soft]&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft/blob/main/openal-soft/COPYING GPL]&lt;br /&gt;
|http://connect.creativelabs.com/openal/default.aspx&lt;br /&gt;
|3D Spatial audio&lt;br /&gt;
|-&lt;br /&gt;
|openjpeg&lt;br /&gt;
|[https://github.com/secondlife/3p-openjpeg 3p-openjpeg]&lt;br /&gt;
|&#039;&#039;[https://github.com/uclouvain/openjpeg/blob/master/LICENSE BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://www.openjpeg.org/&lt;br /&gt;
|An open-source JPEG-2000 library; a slower alternative to Kadaku. Used in the open source release&lt;br /&gt;
|-&lt;br /&gt;
|openssl&lt;br /&gt;
|[https://github.com/secondlife/3p-openssl 3p-openssl]&lt;br /&gt;
|[https://github.com/openssl/openssl/blob/master/LICENSE.txt Apache License]&lt;br /&gt;
|http://www.openssl.org/&lt;br /&gt;
|Provides encryption for sensitive actions such as user login&lt;br /&gt;
|-&lt;br /&gt;
|sdl&lt;br /&gt;
|[https://github.com/secondlife/3p-sdl2 3p-sdl2]&lt;br /&gt;
|[https://github.com/libsdl-org/SDL/blob/main/LICENSE.txt zlib License]&lt;br /&gt;
|http://www.libsdl.org/index.php&lt;br /&gt;
|The Simple DirectMedia Layer libraries are used for handling input and basic window/GL setup on the Linux client.&lt;br /&gt;
|-&lt;br /&gt;
|slvoice&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|This is the Vivox provided executable (renamed SLVoice)&lt;br /&gt;
|-&lt;br /&gt;
|tut&lt;br /&gt;
|[https://github.com/secondlife/3p-tut 3p-tut]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-tut/blob/master/LICENSE.txt BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://tut-framework.sourceforge.net/&lt;br /&gt;
|Unit test framework based on the use of c++ meta template programming. Used to test LL libraries.&lt;br /&gt;
|-&lt;br /&gt;
|xmlrpc_epi&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi 3p-xmlrpc-epi]&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi/blob/master/xmlrpc-epi/COPYING Epinions, Inc License]&lt;br /&gt;
|http://xmlrpc-epi.sourceforge.net/&lt;br /&gt;
|XML-RPC protocol utility&lt;br /&gt;
|-&lt;br /&gt;
|zlib-ng&lt;br /&gt;
|[https://github.com/secondlife/3p-zlib-ng 3p-zlib-ng]&lt;br /&gt;
|[https://github.com/zlib-ng/zlib-ng/blob/develop/LICENSE.md zlib License]&lt;br /&gt;
|http://www.zlib.net/&lt;br /&gt;
|zlib is a lossless data-compression library which is used for a variety of network i/o and file compression.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Redistribution =&lt;br /&gt;
Some files in the SL installation can&#039;t be freely redistributed. This means it&#039;s not possible to simply package and offer for download a modified viewer: some files &#039;&#039;&#039;must not&#039;&#039;&#039; be redistributed. &lt;br /&gt;
&lt;br /&gt;
This page documents the licenses and redistribution requirements of various files used by the viewer.&lt;br /&gt;
&lt;br /&gt;
== Windows ==&lt;br /&gt;
These are the files that need to be shipped with the Windows version&lt;br /&gt;
&lt;br /&gt;
=== Runtime ===&lt;br /&gt;
Microsoft components. It&#039;s not yet known whether they&#039;re redistributable.&lt;br /&gt;
&lt;br /&gt;
* dbghelp.dll: [http://msdn2.microsoft.com/EN-US/library/ms679294.aspx Redistributable]&lt;br /&gt;
* msvcp71.dll: C++ Runtime Library, redistributable    -viewer now using msvcp80.dll when compiled with VC2005&lt;br /&gt;
* msvcr71.dll: C Runtime Library, [http://msdn2.microsoft.com/en-us/library/abx4dbyh(vs.71).aspx Redistributable]   -viewer now using msvcr80.dll when compiled with VC2005&lt;br /&gt;
&lt;br /&gt;
== FONTS ==&lt;br /&gt;
Meta font software files&lt;br /&gt;
&lt;br /&gt;
From the Readme.txt&lt;br /&gt;
 The Meta font software files contained in this folder are the&lt;br /&gt;
 copyrighted property of FSI FontShop International (&amp;quot;FSI&amp;quot;) and are&lt;br /&gt;
 licensed by FSI solely for use by Linden Research, Inc. and by&lt;br /&gt;
 residents or users of Second Life in the Second Life environment,&lt;br /&gt;
 subject to the Second Life Terms of Service.  These Meta font software&lt;br /&gt;
 files may not be copied by residents or developers of Second Life or&lt;br /&gt;
 used by them for any other purpose whatsoever.&lt;br /&gt;
&lt;br /&gt;
*  MtBkLfRg.ttf &lt;br /&gt;
*  MtBdLfRg.ttf&lt;br /&gt;
&lt;br /&gt;
NOTE:  These files must be replaced for an true opensource build&lt;br /&gt;
&lt;br /&gt;
=== Mozilla ===&lt;br /&gt;
The Gecko engine is MPL/GPL/LGPL licensed, which makes it redistributable.&lt;br /&gt;
&lt;br /&gt;
* app_settings/mozilla&lt;br /&gt;
* freebl3.dll&lt;br /&gt;
* gksvggdiplus.dll&lt;br /&gt;
* js3250.dll&lt;br /&gt;
* nspr4.dll&lt;br /&gt;
* nss3.dll&lt;br /&gt;
* nssckbi.dll&lt;br /&gt;
* plc4.dll,&lt;br /&gt;
* plds4.dll&lt;br /&gt;
* smime3.dll&lt;br /&gt;
* softokn3.dll&lt;br /&gt;
* ssl3.dll&lt;br /&gt;
* xpcom.dll&lt;br /&gt;
* xul.dll&lt;br /&gt;
&lt;br /&gt;
=== Kakadu ===&lt;br /&gt;
JPEG2000 library. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;.&lt;br /&gt;
OpenJPEG is an alternative library that is redistributable&lt;br /&gt;
&lt;br /&gt;
* llkdu.dll&lt;br /&gt;
&lt;br /&gt;
Starting with Viewer 2.5, llkdu.dll has been phased out. The libkdu.a and kdu.lib are still not redistributable but can be built from the toolkit provided by Kakadu under a license. How to build a viewer with such a library is documented in [[KDU Implementation]].&lt;br /&gt;
&lt;br /&gt;
=== OpenSSL ===&lt;br /&gt;
SSL library. Redistributable.&lt;br /&gt;
&lt;br /&gt;
* libeay32.dll&lt;br /&gt;
* ssleay32.dll&lt;br /&gt;
&lt;br /&gt;
=== Vivox ===&lt;br /&gt;
Vivox is the component that provides the voice functionality.&lt;br /&gt;
&lt;br /&gt;
* SLVoice.exe: Voice daemon. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* SLVoiceAgent: Exact purpose unknown. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* alut.dll: OpenAL, by Creative Labs. Redistributable.&lt;br /&gt;
* ortp.dll: oRTP project, LGPL. Redistributable.&lt;br /&gt;
* srtp.dll: Secure RTP?&lt;br /&gt;
* vivoxsdk.dll: Vivox Communications API. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* wrap_oal.dll: OpenAL32 by Creative Labs. Redistributable.&lt;br /&gt;
&lt;br /&gt;
=== Unknown Libraries ===&lt;br /&gt;
The purpose and licensing of these files is not known:&lt;br /&gt;
&lt;br /&gt;
* tntk.dll: Probably Vivox related  -see [https://wiki.secondlife.com/wiki/Voice/Technical Voice/Technical] for other info on ViVox files&lt;br /&gt;
&lt;br /&gt;
[[Category:Compiling viewer]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217094</id>
		<title>Third Party Libraries</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217094"/>
		<updated>2024-08-10T18:58:04Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
The following table lists libraries and other third party (3p) dependencies used in constructing the Second Life Viewer.&lt;br /&gt;
&lt;br /&gt;
Most of these (all the open source ones) should be available as prebuilds packaged by and for use with [[Autobuild]].&lt;br /&gt;
&lt;br /&gt;
=== Creating a new third party package ===&lt;br /&gt;
&lt;br /&gt;
Need to create a new third party package? The best route may be to start with an existing package similar to the one you wish to create and modify.&lt;br /&gt;
&lt;br /&gt;
==== Generic Layout ====&lt;br /&gt;
&lt;br /&gt;
3p packages should vendor upstream contents using [https://git-scm.com/book/en/v2/Git-Tools-Submodules git submodules] unless a source git repository for the source dependency is not available.&lt;br /&gt;
A typical 3p package layout looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
.&lt;br /&gt;
├── [vendor submodule]/ - Git submodule containing upstream source&lt;br /&gt;
├── .github/            - CI/CD and repository configuration&lt;br /&gt;
├── autobuild.xml       - Autobuild manifest&lt;br /&gt;
├── build-cmd.sh        - Script used to build the repository&lt;br /&gt;
├── LICENSE             - Repository licensing&lt;br /&gt;
└── README.md           - Repository readme&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Contributor Instructions ====&lt;br /&gt;
&lt;br /&gt;
The general workflow for contributing changes to a 3p library looks like this:&lt;br /&gt;
&lt;br /&gt;
# Fork the repository and make changes. Be sure to test your work locally.&lt;br /&gt;
# Create a PR (Be sure to follow [https://gist.github.com/mikepea/863f63d6e37281e329f8 PR Etiquette] and our [https://github.com/secondlife/.github/blob/main/CODE_OF_CONDUCT.md Code of Conduct])&lt;br /&gt;
# Have this PR reviewed and merged&lt;br /&gt;
# Done! The maintainer should cut a new version as appropriate using the workflow described below.&lt;br /&gt;
&lt;br /&gt;
==== Maintainer Instructions ====&lt;br /&gt;
&lt;br /&gt;
[[File:Screenshot 2024-08-10 at 11-48-23 Releases · secondlife 3p-zlib-ng.png|thumb|top|right|A properly versioned and promoted release of 3p-zlib-ng]]&lt;br /&gt;
&lt;br /&gt;
If you are a 3p package maintainer you will want to follow these instructions to review and release new versions:&lt;br /&gt;
&lt;br /&gt;
# Review PRs quickly, and merge them directly into the default branch once approved&lt;br /&gt;
# Cut a new release by using Github&#039;s Releases feature:&lt;br /&gt;
## Navigate to &#039;&#039;&#039;Repository -&amp;gt; Releases&#039;&#039;&#039; and click &#039;&#039;&#039;&amp;quot;Draft new Release&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
## Click &#039;&#039;&#039;Choose a tag&#039;&#039;&#039; and create a new tag using the &amp;lt;code&amp;gt;vUPSTREAM-rRELEASE&amp;lt;/code&amp;gt; format described below&lt;br /&gt;
## Click &#039;&#039;&#039;Generate release notes&#039;&#039;&#039; and edit them as appropriate. Keep the title of the release equal to the version being used.&lt;br /&gt;
## Click &#039;&#039;Publish release&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
After the release is published, CI/CD should be triggered by the addition of the new tag, and provide a nice set of packages and instructions for consumers to use.&lt;br /&gt;
&lt;br /&gt;
===== Version format =====&lt;br /&gt;
&lt;br /&gt;
3p packages should be released using the following version schema:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Format&lt;br /&gt;
&lt;br /&gt;
  vUPSTREAM-rRELEASE&lt;br /&gt;
&lt;br /&gt;
  Example: v1.0.0-r2 (Second vendored release of v1.0.0 of this library)&lt;br /&gt;
&lt;br /&gt;
  UPSTREAM - Upstream package version. ex. 1.0.0 or 6.7&lt;br /&gt;
  RELEASE - Vendored release number, ex. 1, 2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== List of third party libraries ===&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;3&amp;quot;&lt;br /&gt;
|- style=&amp;quot;background:#ffdead;&amp;quot;&lt;br /&gt;
!Library&lt;br /&gt;
!Repository&lt;br /&gt;
!License&lt;br /&gt;
!Upstream Home&lt;br /&gt;
!Description&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|apr_suite&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/ 3p-apr]&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/blob/main/apr/LICENSE Apache License]&lt;br /&gt;
|http://apr.apache.org/&lt;br /&gt;
|&amp;quot;A set of C functions which provide a portable and consistent interface into operating system - or simply widely useful - functionality. We primarily use it for threading and socket i/o. This is actually three packages, apr, apr-util and apr-iconv.&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|boost&lt;br /&gt;
|[https://github.com/secondlife/3p-boost 3p-boost]&lt;br /&gt;
|[https://github.com/boostorg/boost/blob/master/LICENSE_1_0.txt Boost License]&lt;br /&gt;
|http://www.boost.org/&lt;br /&gt;
|A set of portable C++ libraries which provide a wide set of functionality. Used primarily for tokenization.&lt;br /&gt;
|-&lt;br /&gt;
|curl&lt;br /&gt;
|[https://github.com/secondlife/3p-curl 3p-curl]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-curl/blob/master/curl/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
| http://curl.haxx.se/libcurl/&lt;br /&gt;
|Handles moving data across the net in many different protocols. Used to GET/POST/PUT/DELETE web resources.&lt;br /&gt;
|-&lt;br /&gt;
|expat&lt;br /&gt;
|[https://github.com/secondlife/3p-expat 3p-expat]&lt;br /&gt;
|[https://github.com/libexpat/libexpat/blob/master/COPYING MIT License]&lt;br /&gt;
|https://github.com/libexpat/libexpat&lt;br /&gt;
|XML parser.&lt;br /&gt;
|-&lt;br /&gt;
|freetype&lt;br /&gt;
|[https://github.com/secondlife/3p-freetype 3p-freetype]&lt;br /&gt;
|[https://github.com/freetype/freetype/blob/master/LICENSE.TXT Freetype License and Others]&lt;br /&gt;
|http://www.freetype.org/&lt;br /&gt;
|Font engine.&lt;br /&gt;
|-&lt;br /&gt;
|glh_linear&lt;br /&gt;
|[https://github.com/secondlife/3p-glh_linear 3p-glh_linear]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|nVidia NVParse SDK: platform-independent C++ Apple OpenGL helper library&lt;br /&gt;
|-&lt;br /&gt;
|gstreamer&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-gstreamer 3p-gstreamer]&lt;br /&gt;
|Open&lt;br /&gt;
|http://gstreamer.freedesktop.org&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|havok&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|jpeglib&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-jpeglib 3p-jpeglib]&lt;br /&gt;
|Open&lt;br /&gt;
|http://www.ijg.org/&lt;br /&gt;
|JPEG decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|kdu&lt;br /&gt;
|[https://hg.lindenlab.com/lindenlab/3p-kdu-private 3p-kdu-private]&amp;lt;sup&amp;gt;[[#PrivateRepo|LL]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|Commercial&lt;br /&gt;
|http://www.kakadusoftware.com&lt;br /&gt;
|Kakadu (KDU) JPEG-2000 decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|libpng&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-libpng 3p-libpng]&lt;br /&gt;
|Open&lt;br /&gt;
| http://www.libpng.org/pub/png/libpng.html&lt;br /&gt;
|PNG image library.&lt;br /&gt;
|-&lt;br /&gt;
|libxml2&lt;br /&gt;
|[https://github.com/secondlife/3p-libxml2 3p-libxml2]&lt;br /&gt;
|[https://gitlab.gnome.org/GNOME/libxml2/-/blob/master/Copyright?ref_type=heads MIT License]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|ndofdev&lt;br /&gt;
|[https://github.com/secondlife/3p-libndofdev 3p-libndofdev]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-libndofdev/blob/master/libndofdev/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|Provides joystick driver support (mac and win) for the 3DConnexion SpaceNavigator.&lt;br /&gt;
|-&lt;br /&gt;
|ogg_vorbis&lt;br /&gt;
|[https://github.com/secondlife/3p-ogg_vorbis 3p-ogvorbis]&lt;br /&gt;
|&#039;&#039;[https://github.com/xiph/ogg/blob/master/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://xiph.org/ogg/&lt;br /&gt;
|Audio codec and playback.&lt;br /&gt;
|-&lt;br /&gt;
|openal&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft 3p-openal-soft]&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft/blob/main/openal-soft/COPYING GPL]&lt;br /&gt;
|http://connect.creativelabs.com/openal/default.aspx&lt;br /&gt;
|3D Spatial audio&lt;br /&gt;
|-&lt;br /&gt;
|openjpeg&lt;br /&gt;
|[https://github.com/secondlife/3p-openjpeg 3p-openjpeg]&lt;br /&gt;
|&#039;&#039;[https://github.com/uclouvain/openjpeg/blob/master/LICENSE BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://www.openjpeg.org/&lt;br /&gt;
|An open-source JPEG-2000 library; a slower alternative to Kadaku. Used in the open source release&lt;br /&gt;
|-&lt;br /&gt;
|openssl&lt;br /&gt;
|[https://github.com/secondlife/3p-openssl 3p-openssl]&lt;br /&gt;
|[https://github.com/openssl/openssl/blob/master/LICENSE.txt Apache License]&lt;br /&gt;
|http://www.openssl.org/&lt;br /&gt;
|Provides encryption for sensitive actions such as user login&lt;br /&gt;
|-&lt;br /&gt;
|sdl&lt;br /&gt;
|[https://github.com/secondlife/3p-sdl2 3p-sdl2]&lt;br /&gt;
|[https://github.com/libsdl-org/SDL/blob/main/LICENSE.txt zlib License]&lt;br /&gt;
|http://www.libsdl.org/index.php&lt;br /&gt;
|The Simple DirectMedia Layer libraries are used for handling input and basic window/GL setup on the Linux client.&lt;br /&gt;
|-&lt;br /&gt;
|slvoice&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|This is the Vivox provided executable (renamed SLVoice)&lt;br /&gt;
|-&lt;br /&gt;
|tut&lt;br /&gt;
|[https://github.com/secondlife/3p-tut 3p-tut]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-tut/blob/master/LICENSE.txt BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://tut-framework.sourceforge.net/&lt;br /&gt;
|Unit test framework based on the use of c++ meta template programming. Used to test LL libraries.&lt;br /&gt;
|-&lt;br /&gt;
|xmlrpc_epi&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi 3p-xmlrpc-epi]&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi/blob/master/xmlrpc-epi/COPYING Epinions, Inc License]&lt;br /&gt;
|http://xmlrpc-epi.sourceforge.net/&lt;br /&gt;
|XML-RPC protocol utility&lt;br /&gt;
|-&lt;br /&gt;
|zlib-ng&lt;br /&gt;
|[https://github.com/secondlife/3p-zlib-ng 3p-zlib-ng]&lt;br /&gt;
|[https://github.com/zlib-ng/zlib-ng/blob/develop/LICENSE.md zlib License]&lt;br /&gt;
|http://www.zlib.net/&lt;br /&gt;
|zlib is a lossless data-compression library which is used for a variety of network i/o and file compression.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Redistribution =&lt;br /&gt;
Some files in the SL installation can&#039;t be freely redistributed. This means it&#039;s not possible to simply package and offer for download a modified viewer: some files &#039;&#039;&#039;must not&#039;&#039;&#039; be redistributed. &lt;br /&gt;
&lt;br /&gt;
This page documents the licenses and redistribution requirements of various files used by the viewer.&lt;br /&gt;
&lt;br /&gt;
== Windows ==&lt;br /&gt;
These are the files that need to be shipped with the Windows version&lt;br /&gt;
&lt;br /&gt;
=== Runtime ===&lt;br /&gt;
Microsoft components. It&#039;s not yet known whether they&#039;re redistributable.&lt;br /&gt;
&lt;br /&gt;
* dbghelp.dll: [http://msdn2.microsoft.com/EN-US/library/ms679294.aspx Redistributable]&lt;br /&gt;
* msvcp71.dll: C++ Runtime Library, redistributable    -viewer now using msvcp80.dll when compiled with VC2005&lt;br /&gt;
* msvcr71.dll: C Runtime Library, [http://msdn2.microsoft.com/en-us/library/abx4dbyh(vs.71).aspx Redistributable]   -viewer now using msvcr80.dll when compiled with VC2005&lt;br /&gt;
&lt;br /&gt;
== FONTS ==&lt;br /&gt;
Meta font software files&lt;br /&gt;
&lt;br /&gt;
From the Readme.txt&lt;br /&gt;
 The Meta font software files contained in this folder are the&lt;br /&gt;
 copyrighted property of FSI FontShop International (&amp;quot;FSI&amp;quot;) and are&lt;br /&gt;
 licensed by FSI solely for use by Linden Research, Inc. and by&lt;br /&gt;
 residents or users of Second Life in the Second Life environment,&lt;br /&gt;
 subject to the Second Life Terms of Service.  These Meta font software&lt;br /&gt;
 files may not be copied by residents or developers of Second Life or&lt;br /&gt;
 used by them for any other purpose whatsoever.&lt;br /&gt;
&lt;br /&gt;
*  MtBkLfRg.ttf &lt;br /&gt;
*  MtBdLfRg.ttf&lt;br /&gt;
&lt;br /&gt;
NOTE:  These files must be replaced for an true opensource build&lt;br /&gt;
&lt;br /&gt;
=== Mozilla ===&lt;br /&gt;
The Gecko engine is MPL/GPL/LGPL licensed, which makes it redistributable.&lt;br /&gt;
&lt;br /&gt;
* app_settings/mozilla&lt;br /&gt;
* freebl3.dll&lt;br /&gt;
* gksvggdiplus.dll&lt;br /&gt;
* js3250.dll&lt;br /&gt;
* nspr4.dll&lt;br /&gt;
* nss3.dll&lt;br /&gt;
* nssckbi.dll&lt;br /&gt;
* plc4.dll,&lt;br /&gt;
* plds4.dll&lt;br /&gt;
* smime3.dll&lt;br /&gt;
* softokn3.dll&lt;br /&gt;
* ssl3.dll&lt;br /&gt;
* xpcom.dll&lt;br /&gt;
* xul.dll&lt;br /&gt;
&lt;br /&gt;
=== Kakadu ===&lt;br /&gt;
JPEG2000 library. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;.&lt;br /&gt;
OpenJPEG is an alternative library that is redistributable&lt;br /&gt;
&lt;br /&gt;
* llkdu.dll&lt;br /&gt;
&lt;br /&gt;
Starting with Viewer 2.5, llkdu.dll has been phased out. The libkdu.a and kdu.lib are still not redistributable but can be built from the toolkit provided by Kakadu under a license. How to build a viewer with such a library is documented in [[KDU Implementation]].&lt;br /&gt;
&lt;br /&gt;
=== OpenSSL ===&lt;br /&gt;
SSL library. Redistributable.&lt;br /&gt;
&lt;br /&gt;
* libeay32.dll&lt;br /&gt;
* ssleay32.dll&lt;br /&gt;
&lt;br /&gt;
=== Vivox ===&lt;br /&gt;
Vivox is the component that provides the voice functionality.&lt;br /&gt;
&lt;br /&gt;
* SLVoice.exe: Voice daemon. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* SLVoiceAgent: Exact purpose unknown. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* alut.dll: OpenAL, by Creative Labs. Redistributable.&lt;br /&gt;
* ortp.dll: oRTP project, LGPL. Redistributable.&lt;br /&gt;
* srtp.dll: Secure RTP?&lt;br /&gt;
* vivoxsdk.dll: Vivox Communications API. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* wrap_oal.dll: OpenAL32 by Creative Labs. Redistributable.&lt;br /&gt;
&lt;br /&gt;
=== Unknown Libraries ===&lt;br /&gt;
The purpose and licensing of these files is not known:&lt;br /&gt;
&lt;br /&gt;
* tntk.dll: Probably Vivox related  -see [https://wiki.secondlife.com/wiki/Voice/Technical Voice/Technical] for other info on ViVox files&lt;br /&gt;
&lt;br /&gt;
[[Category:Compiling viewer]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217093</id>
		<title>Third Party Libraries</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217093"/>
		<updated>2024-08-10T18:57:41Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
The following table lists libraries and other third party (3p) dependencies used in constructing the Second Life Viewer.&lt;br /&gt;
&lt;br /&gt;
Most of these (all the open source ones) should be available as prebuilds packaged by and for use with [[Autobuild]].&lt;br /&gt;
&lt;br /&gt;
Anywhere that the following disagrees with the autobuild.xml file in the viewer source, that file is correct.&lt;br /&gt;
&lt;br /&gt;
=== Creating a new third party package ===&lt;br /&gt;
&lt;br /&gt;
Need to create a new third party package? The best route may be to start with an existing package similar to the one you wish to create and modify.&lt;br /&gt;
&lt;br /&gt;
==== Generic Layout ====&lt;br /&gt;
&lt;br /&gt;
3p packages should vendor upstream contents using [https://git-scm.com/book/en/v2/Git-Tools-Submodules git submodules] unless a source git repository for the source dependency is not available.&lt;br /&gt;
A typical 3p package layout looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
.&lt;br /&gt;
├── [vendor submodule]/ - Git submodule containing upstream source&lt;br /&gt;
├── .github/            - CI/CD and repository configuration&lt;br /&gt;
├── autobuild.xml       - Autobuild manifest&lt;br /&gt;
├── build-cmd.sh        - Script used to build the repository&lt;br /&gt;
├── LICENSE             - Repository licensing&lt;br /&gt;
└── README.md           - Repository readme&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Contributor Instructions ====&lt;br /&gt;
&lt;br /&gt;
The general workflow for contributing changes to a 3p library looks like this:&lt;br /&gt;
&lt;br /&gt;
# Fork the repository and make changes. Be sure to test your work locally.&lt;br /&gt;
# Create a PR (Be sure to follow [https://gist.github.com/mikepea/863f63d6e37281e329f8 PR Etiquette] and our [https://github.com/secondlife/.github/blob/main/CODE_OF_CONDUCT.md Code of Conduct])&lt;br /&gt;
# Have this PR reviewed and merged&lt;br /&gt;
# Done! The maintainer should cut a new version as appropriate using the workflow described below.&lt;br /&gt;
&lt;br /&gt;
==== Maintainer Instructions ====&lt;br /&gt;
&lt;br /&gt;
[[File:Screenshot 2024-08-10 at 11-48-23 Releases · secondlife 3p-zlib-ng.png|thumb|top|right|A properly versioned and promoted release of 3p-zlib-ng]]&lt;br /&gt;
&lt;br /&gt;
If you are a 3p package maintainer you will want to follow these instructions to review and release new versions:&lt;br /&gt;
&lt;br /&gt;
# Review PRs quickly, and merge them directly into the default branch once approved&lt;br /&gt;
# Cut a new release by using Github&#039;s Releases feature:&lt;br /&gt;
## Navigate to &#039;&#039;&#039;Repository -&amp;gt; Releases&#039;&#039;&#039; and click &#039;&#039;&#039;&amp;quot;Draft new Release&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
## Click &#039;&#039;&#039;Choose a tag&#039;&#039;&#039; and create a new tag using the &amp;lt;code&amp;gt;vUPSTREAM-rRELEASE&amp;lt;/code&amp;gt; format described below&lt;br /&gt;
## Click &#039;&#039;&#039;Generate release notes&#039;&#039;&#039; and edit them as appropriate. Keep the title of the release equal to the version being used.&lt;br /&gt;
## Click &#039;&#039;Publish release&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
After the release is published, CI/CD should be triggered by the addition of the new tag, and provide a nice set of packages and instructions for consumers to use.&lt;br /&gt;
&lt;br /&gt;
===== Version format =====&lt;br /&gt;
&lt;br /&gt;
3p packages should be released using the following version schema:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Format&lt;br /&gt;
&lt;br /&gt;
  vUPSTREAM-rRELEASE&lt;br /&gt;
&lt;br /&gt;
  Example: v1.0.0-r2 (Second vendored release of v1.0.0 of this library)&lt;br /&gt;
&lt;br /&gt;
  UPSTREAM - Upstream package version. ex. 1.0.0 or 6.7&lt;br /&gt;
  RELEASE - Vendored release number, ex. 1, 2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== List of third party libraries ===&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;3&amp;quot;&lt;br /&gt;
|- style=&amp;quot;background:#ffdead;&amp;quot;&lt;br /&gt;
!Library&lt;br /&gt;
!Repository&lt;br /&gt;
!License&lt;br /&gt;
!Upstream Home&lt;br /&gt;
!Description&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|apr_suite&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/ 3p-apr]&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/blob/main/apr/LICENSE Apache License]&lt;br /&gt;
|http://apr.apache.org/&lt;br /&gt;
|&amp;quot;A set of C functions which provide a portable and consistent interface into operating system - or simply widely useful - functionality. We primarily use it for threading and socket i/o. This is actually three packages, apr, apr-util and apr-iconv.&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|boost&lt;br /&gt;
|[https://github.com/secondlife/3p-boost 3p-boost]&lt;br /&gt;
|[https://github.com/boostorg/boost/blob/master/LICENSE_1_0.txt Boost License]&lt;br /&gt;
|http://www.boost.org/&lt;br /&gt;
|A set of portable C++ libraries which provide a wide set of functionality. Used primarily for tokenization.&lt;br /&gt;
|-&lt;br /&gt;
|curl&lt;br /&gt;
|[https://github.com/secondlife/3p-curl 3p-curl]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-curl/blob/master/curl/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
| http://curl.haxx.se/libcurl/&lt;br /&gt;
|Handles moving data across the net in many different protocols. Used to GET/POST/PUT/DELETE web resources.&lt;br /&gt;
|-&lt;br /&gt;
|expat&lt;br /&gt;
|[https://github.com/secondlife/3p-expat 3p-expat]&lt;br /&gt;
|[https://github.com/libexpat/libexpat/blob/master/COPYING MIT License]&lt;br /&gt;
|https://github.com/libexpat/libexpat&lt;br /&gt;
|XML parser.&lt;br /&gt;
|-&lt;br /&gt;
|freetype&lt;br /&gt;
|[https://github.com/secondlife/3p-freetype 3p-freetype]&lt;br /&gt;
|[https://github.com/freetype/freetype/blob/master/LICENSE.TXT Freetype License and Others]&lt;br /&gt;
|http://www.freetype.org/&lt;br /&gt;
|Font engine.&lt;br /&gt;
|-&lt;br /&gt;
|glh_linear&lt;br /&gt;
|[https://github.com/secondlife/3p-glh_linear 3p-glh_linear]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|nVidia NVParse SDK: platform-independent C++ Apple OpenGL helper library&lt;br /&gt;
|-&lt;br /&gt;
|gstreamer&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-gstreamer 3p-gstreamer]&lt;br /&gt;
|Open&lt;br /&gt;
|http://gstreamer.freedesktop.org&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|havok&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|jpeglib&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-jpeglib 3p-jpeglib]&lt;br /&gt;
|Open&lt;br /&gt;
|http://www.ijg.org/&lt;br /&gt;
|JPEG decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|kdu&lt;br /&gt;
|[https://hg.lindenlab.com/lindenlab/3p-kdu-private 3p-kdu-private]&amp;lt;sup&amp;gt;[[#PrivateRepo|LL]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|Commercial&lt;br /&gt;
|http://www.kakadusoftware.com&lt;br /&gt;
|Kakadu (KDU) JPEG-2000 decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|libpng&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-libpng 3p-libpng]&lt;br /&gt;
|Open&lt;br /&gt;
| http://www.libpng.org/pub/png/libpng.html&lt;br /&gt;
|PNG image library.&lt;br /&gt;
|-&lt;br /&gt;
|libxml2&lt;br /&gt;
|[https://github.com/secondlife/3p-libxml2 3p-libxml2]&lt;br /&gt;
|[https://gitlab.gnome.org/GNOME/libxml2/-/blob/master/Copyright?ref_type=heads MIT License]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|ndofdev&lt;br /&gt;
|[https://github.com/secondlife/3p-libndofdev 3p-libndofdev]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-libndofdev/blob/master/libndofdev/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|Provides joystick driver support (mac and win) for the 3DConnexion SpaceNavigator.&lt;br /&gt;
|-&lt;br /&gt;
|ogg_vorbis&lt;br /&gt;
|[https://github.com/secondlife/3p-ogg_vorbis 3p-ogvorbis]&lt;br /&gt;
|&#039;&#039;[https://github.com/xiph/ogg/blob/master/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://xiph.org/ogg/&lt;br /&gt;
|Audio codec and playback.&lt;br /&gt;
|-&lt;br /&gt;
|openal&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft 3p-openal-soft]&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft/blob/main/openal-soft/COPYING GPL]&lt;br /&gt;
|http://connect.creativelabs.com/openal/default.aspx&lt;br /&gt;
|3D Spatial audio&lt;br /&gt;
|-&lt;br /&gt;
|openjpeg&lt;br /&gt;
|[https://github.com/secondlife/3p-openjpeg 3p-openjpeg]&lt;br /&gt;
|&#039;&#039;[https://github.com/uclouvain/openjpeg/blob/master/LICENSE BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://www.openjpeg.org/&lt;br /&gt;
|An open-source JPEG-2000 library; a slower alternative to Kadaku. Used in the open source release&lt;br /&gt;
|-&lt;br /&gt;
|openssl&lt;br /&gt;
|[https://github.com/secondlife/3p-openssl 3p-openssl]&lt;br /&gt;
|[https://github.com/openssl/openssl/blob/master/LICENSE.txt Apache License]&lt;br /&gt;
|http://www.openssl.org/&lt;br /&gt;
|Provides encryption for sensitive actions such as user login&lt;br /&gt;
|-&lt;br /&gt;
|sdl&lt;br /&gt;
|[https://github.com/secondlife/3p-sdl2 3p-sdl2]&lt;br /&gt;
|[https://github.com/libsdl-org/SDL/blob/main/LICENSE.txt zlib License]&lt;br /&gt;
|http://www.libsdl.org/index.php&lt;br /&gt;
|The Simple DirectMedia Layer libraries are used for handling input and basic window/GL setup on the Linux client.&lt;br /&gt;
|-&lt;br /&gt;
|slvoice&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|This is the Vivox provided executable (renamed SLVoice)&lt;br /&gt;
|-&lt;br /&gt;
|tut&lt;br /&gt;
|[https://github.com/secondlife/3p-tut 3p-tut]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-tut/blob/master/LICENSE.txt BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://tut-framework.sourceforge.net/&lt;br /&gt;
|Unit test framework based on the use of c++ meta template programming. Used to test LL libraries.&lt;br /&gt;
|-&lt;br /&gt;
|xmlrpc_epi&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi 3p-xmlrpc-epi]&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi/blob/master/xmlrpc-epi/COPYING Epinions, Inc License]&lt;br /&gt;
|http://xmlrpc-epi.sourceforge.net/&lt;br /&gt;
|XML-RPC protocol utility&lt;br /&gt;
|-&lt;br /&gt;
|zlib-ng&lt;br /&gt;
|[https://github.com/secondlife/3p-zlib-ng 3p-zlib-ng]&lt;br /&gt;
|[https://github.com/zlib-ng/zlib-ng/blob/develop/LICENSE.md zlib License]&lt;br /&gt;
|http://www.zlib.net/&lt;br /&gt;
|zlib is a lossless data-compression library which is used for a variety of network i/o and file compression.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Redistribution =&lt;br /&gt;
Some files in the SL installation can&#039;t be freely redistributed. This means it&#039;s not possible to simply package and offer for download a modified viewer: some files &#039;&#039;&#039;must not&#039;&#039;&#039; be redistributed. &lt;br /&gt;
&lt;br /&gt;
This page documents the licenses and redistribution requirements of various files used by the viewer.&lt;br /&gt;
&lt;br /&gt;
== Windows ==&lt;br /&gt;
These are the files that need to be shipped with the Windows version&lt;br /&gt;
&lt;br /&gt;
=== Runtime ===&lt;br /&gt;
Microsoft components. It&#039;s not yet known whether they&#039;re redistributable.&lt;br /&gt;
&lt;br /&gt;
* dbghelp.dll: [http://msdn2.microsoft.com/EN-US/library/ms679294.aspx Redistributable]&lt;br /&gt;
* msvcp71.dll: C++ Runtime Library, redistributable    -viewer now using msvcp80.dll when compiled with VC2005&lt;br /&gt;
* msvcr71.dll: C Runtime Library, [http://msdn2.microsoft.com/en-us/library/abx4dbyh(vs.71).aspx Redistributable]   -viewer now using msvcr80.dll when compiled with VC2005&lt;br /&gt;
&lt;br /&gt;
== FONTS ==&lt;br /&gt;
Meta font software files&lt;br /&gt;
&lt;br /&gt;
From the Readme.txt&lt;br /&gt;
 The Meta font software files contained in this folder are the&lt;br /&gt;
 copyrighted property of FSI FontShop International (&amp;quot;FSI&amp;quot;) and are&lt;br /&gt;
 licensed by FSI solely for use by Linden Research, Inc. and by&lt;br /&gt;
 residents or users of Second Life in the Second Life environment,&lt;br /&gt;
 subject to the Second Life Terms of Service.  These Meta font software&lt;br /&gt;
 files may not be copied by residents or developers of Second Life or&lt;br /&gt;
 used by them for any other purpose whatsoever.&lt;br /&gt;
&lt;br /&gt;
*  MtBkLfRg.ttf &lt;br /&gt;
*  MtBdLfRg.ttf&lt;br /&gt;
&lt;br /&gt;
NOTE:  These files must be replaced for an true opensource build&lt;br /&gt;
&lt;br /&gt;
=== Mozilla ===&lt;br /&gt;
The Gecko engine is MPL/GPL/LGPL licensed, which makes it redistributable.&lt;br /&gt;
&lt;br /&gt;
* app_settings/mozilla&lt;br /&gt;
* freebl3.dll&lt;br /&gt;
* gksvggdiplus.dll&lt;br /&gt;
* js3250.dll&lt;br /&gt;
* nspr4.dll&lt;br /&gt;
* nss3.dll&lt;br /&gt;
* nssckbi.dll&lt;br /&gt;
* plc4.dll,&lt;br /&gt;
* plds4.dll&lt;br /&gt;
* smime3.dll&lt;br /&gt;
* softokn3.dll&lt;br /&gt;
* ssl3.dll&lt;br /&gt;
* xpcom.dll&lt;br /&gt;
* xul.dll&lt;br /&gt;
&lt;br /&gt;
=== Kakadu ===&lt;br /&gt;
JPEG2000 library. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;.&lt;br /&gt;
OpenJPEG is an alternative library that is redistributable&lt;br /&gt;
&lt;br /&gt;
* llkdu.dll&lt;br /&gt;
&lt;br /&gt;
Starting with Viewer 2.5, llkdu.dll has been phased out. The libkdu.a and kdu.lib are still not redistributable but can be built from the toolkit provided by Kakadu under a license. How to build a viewer with such a library is documented in [[KDU Implementation]].&lt;br /&gt;
&lt;br /&gt;
=== OpenSSL ===&lt;br /&gt;
SSL library. Redistributable.&lt;br /&gt;
&lt;br /&gt;
* libeay32.dll&lt;br /&gt;
* ssleay32.dll&lt;br /&gt;
&lt;br /&gt;
=== Vivox ===&lt;br /&gt;
Vivox is the component that provides the voice functionality.&lt;br /&gt;
&lt;br /&gt;
* SLVoice.exe: Voice daemon. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* SLVoiceAgent: Exact purpose unknown. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* alut.dll: OpenAL, by Creative Labs. Redistributable.&lt;br /&gt;
* ortp.dll: oRTP project, LGPL. Redistributable.&lt;br /&gt;
* srtp.dll: Secure RTP?&lt;br /&gt;
* vivoxsdk.dll: Vivox Communications API. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* wrap_oal.dll: OpenAL32 by Creative Labs. Redistributable.&lt;br /&gt;
&lt;br /&gt;
=== Unknown Libraries ===&lt;br /&gt;
The purpose and licensing of these files is not known:&lt;br /&gt;
&lt;br /&gt;
* tntk.dll: Probably Vivox related  -see [https://wiki.secondlife.com/wiki/Voice/Technical Voice/Technical] for other info on ViVox files&lt;br /&gt;
&lt;br /&gt;
[[Category:Compiling viewer]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217092</id>
		<title>Third Party Libraries</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217092"/>
		<updated>2024-08-10T18:56:54Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
The following table lists libraries and other third party (3p) dependencies used in constructing the Second Life Viewer.&lt;br /&gt;
&lt;br /&gt;
Most of these (all the open source ones) should be available as prebuilds packaged by and for use with [[Autobuild]].&lt;br /&gt;
&lt;br /&gt;
Anywhere that the following disagrees with the autobuild.xml file in the viewer source, that file is correct.&lt;br /&gt;
&lt;br /&gt;
=== Creating a new third party package ===&lt;br /&gt;
&lt;br /&gt;
Need to create a new third party package? The best route may be to start with an existing package similar to the one you wish to create and modify.&lt;br /&gt;
&lt;br /&gt;
==== Generic Layout ====&lt;br /&gt;
&lt;br /&gt;
3p packages should vendor upstream contents using [https://git-scm.com/book/en/v2/Git-Tools-Submodules git submodules] unless a source git repository for the source dependency is not available.&lt;br /&gt;
A typical 3p package layout looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
.&lt;br /&gt;
├── [vendor submodule]/ - Git submodule containing upstream source&lt;br /&gt;
├── .github/            - CI/CD and repository configuration&lt;br /&gt;
├── autobuild.xml       - Autobuild manifest&lt;br /&gt;
├── build-cmd.sh        - Script used to build the repository&lt;br /&gt;
├── LICENSE             - Repository licensing&lt;br /&gt;
└── README.md           - Repository readme&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Contributor Instructions ====&lt;br /&gt;
&lt;br /&gt;
The general workflow for contributing changes to a 3p library looks like this:&lt;br /&gt;
&lt;br /&gt;
# Fork the repository and make changes. Be sure to test your work locally.&lt;br /&gt;
# Create a PR (Be sure to follow [https://gist.github.com/mikepea/863f63d6e37281e329f8 PR Etiquette] and our [https://github.com/secondlife/.github/blob/main/CODE_OF_CONDUCT.md Code of Conduct])&lt;br /&gt;
# Have this PR reviewed and merged&lt;br /&gt;
# Done! The maintainer should cut a new version as appropriate using the workflow described below.&lt;br /&gt;
&lt;br /&gt;
==== Maintainer Instructions ====&lt;br /&gt;
&lt;br /&gt;
[[File:Screenshot 2024-08-10 at 11-48-23 Releases · secondlife 3p-zlib-ng.png|thumb|top|right|A properly versioned and promoted release of 3p-zlib-ng]]&lt;br /&gt;
&lt;br /&gt;
If you are a 3p package maintainer you will want to follow these instructions to review and release new versions:&lt;br /&gt;
&lt;br /&gt;
# Review PRs quickly, and merge them directly into the default branch once approved&lt;br /&gt;
# Cut a new release by using Github&#039;s Releases feature:&lt;br /&gt;
## Navigate to &#039;&#039;&#039;Repository -&amp;gt; Releases&#039;&#039;&#039; and click &#039;&#039;&#039;&amp;quot;Draft new Release&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
## Click &#039;&#039;&#039;Choose a tag&#039;&#039;&#039; and create a new tag using the format &amp;lt;code&amp;gt;v[vendor version]-[release num]&amp;lt;/code&amp;gt; described below&lt;br /&gt;
## Click &#039;&#039;&#039;Generate release notes&#039;&#039;&#039; and edit them as appropriate. Keep the title of the release equal to the version being used.&lt;br /&gt;
## Click &#039;&#039;Publish release&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
After the release is published, CI/CD should be triggered by the addition of the new tag, and provide a nice set of packages and instructions for consumers to use.&lt;br /&gt;
&lt;br /&gt;
===== Version format =====&lt;br /&gt;
&lt;br /&gt;
3p packages should be released using the following version schema:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Format&lt;br /&gt;
&lt;br /&gt;
  vUPSTREAM-rRELEASE&lt;br /&gt;
&lt;br /&gt;
  Example: v1.0.0-r2 (Second vendored release of v1.0.0 of this library)&lt;br /&gt;
&lt;br /&gt;
  UPSTREAM - Upstream package version. ex. 1.0.0 or 6.7&lt;br /&gt;
  RELEASE - Vendored release number, ex. 1, 2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== List of third party libraries ===&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;3&amp;quot;&lt;br /&gt;
|- style=&amp;quot;background:#ffdead;&amp;quot;&lt;br /&gt;
!Library&lt;br /&gt;
!Repository&lt;br /&gt;
!License&lt;br /&gt;
!Upstream Home&lt;br /&gt;
!Description&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|apr_suite&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/ 3p-apr]&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/blob/main/apr/LICENSE Apache License]&lt;br /&gt;
|http://apr.apache.org/&lt;br /&gt;
|&amp;quot;A set of C functions which provide a portable and consistent interface into operating system - or simply widely useful - functionality. We primarily use it for threading and socket i/o. This is actually three packages, apr, apr-util and apr-iconv.&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|boost&lt;br /&gt;
|[https://github.com/secondlife/3p-boost 3p-boost]&lt;br /&gt;
|[https://github.com/boostorg/boost/blob/master/LICENSE_1_0.txt Boost License]&lt;br /&gt;
|http://www.boost.org/&lt;br /&gt;
|A set of portable C++ libraries which provide a wide set of functionality. Used primarily for tokenization.&lt;br /&gt;
|-&lt;br /&gt;
|curl&lt;br /&gt;
|[https://github.com/secondlife/3p-curl 3p-curl]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-curl/blob/master/curl/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
| http://curl.haxx.se/libcurl/&lt;br /&gt;
|Handles moving data across the net in many different protocols. Used to GET/POST/PUT/DELETE web resources.&lt;br /&gt;
|-&lt;br /&gt;
|expat&lt;br /&gt;
|[https://github.com/secondlife/3p-expat 3p-expat]&lt;br /&gt;
|[https://github.com/libexpat/libexpat/blob/master/COPYING MIT License]&lt;br /&gt;
|https://github.com/libexpat/libexpat&lt;br /&gt;
|XML parser.&lt;br /&gt;
|-&lt;br /&gt;
|freetype&lt;br /&gt;
|[https://github.com/secondlife/3p-freetype 3p-freetype]&lt;br /&gt;
|[https://github.com/freetype/freetype/blob/master/LICENSE.TXT Freetype License and Others]&lt;br /&gt;
|http://www.freetype.org/&lt;br /&gt;
|Font engine.&lt;br /&gt;
|-&lt;br /&gt;
|glh_linear&lt;br /&gt;
|[https://github.com/secondlife/3p-glh_linear 3p-glh_linear]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|nVidia NVParse SDK: platform-independent C++ Apple OpenGL helper library&lt;br /&gt;
|-&lt;br /&gt;
|gstreamer&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-gstreamer 3p-gstreamer]&lt;br /&gt;
|Open&lt;br /&gt;
|http://gstreamer.freedesktop.org&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|havok&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|jpeglib&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-jpeglib 3p-jpeglib]&lt;br /&gt;
|Open&lt;br /&gt;
|http://www.ijg.org/&lt;br /&gt;
|JPEG decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|kdu&lt;br /&gt;
|[https://hg.lindenlab.com/lindenlab/3p-kdu-private 3p-kdu-private]&amp;lt;sup&amp;gt;[[#PrivateRepo|LL]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|Commercial&lt;br /&gt;
|http://www.kakadusoftware.com&lt;br /&gt;
|Kakadu (KDU) JPEG-2000 decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|libpng&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-libpng 3p-libpng]&lt;br /&gt;
|Open&lt;br /&gt;
| http://www.libpng.org/pub/png/libpng.html&lt;br /&gt;
|PNG image library.&lt;br /&gt;
|-&lt;br /&gt;
|libxml2&lt;br /&gt;
|[https://github.com/secondlife/3p-libxml2 3p-libxml2]&lt;br /&gt;
|[https://gitlab.gnome.org/GNOME/libxml2/-/blob/master/Copyright?ref_type=heads MIT License]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|ndofdev&lt;br /&gt;
|[https://github.com/secondlife/3p-libndofdev 3p-libndofdev]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-libndofdev/blob/master/libndofdev/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|Provides joystick driver support (mac and win) for the 3DConnexion SpaceNavigator.&lt;br /&gt;
|-&lt;br /&gt;
|ogg_vorbis&lt;br /&gt;
|[https://github.com/secondlife/3p-ogg_vorbis 3p-ogvorbis]&lt;br /&gt;
|&#039;&#039;[https://github.com/xiph/ogg/blob/master/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://xiph.org/ogg/&lt;br /&gt;
|Audio codec and playback.&lt;br /&gt;
|-&lt;br /&gt;
|openal&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft 3p-openal-soft]&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft/blob/main/openal-soft/COPYING GPL]&lt;br /&gt;
|http://connect.creativelabs.com/openal/default.aspx&lt;br /&gt;
|3D Spatial audio&lt;br /&gt;
|-&lt;br /&gt;
|openjpeg&lt;br /&gt;
|[https://github.com/secondlife/3p-openjpeg 3p-openjpeg]&lt;br /&gt;
|&#039;&#039;[https://github.com/uclouvain/openjpeg/blob/master/LICENSE BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://www.openjpeg.org/&lt;br /&gt;
|An open-source JPEG-2000 library; a slower alternative to Kadaku. Used in the open source release&lt;br /&gt;
|-&lt;br /&gt;
|openssl&lt;br /&gt;
|[https://github.com/secondlife/3p-openssl 3p-openssl]&lt;br /&gt;
|[https://github.com/openssl/openssl/blob/master/LICENSE.txt Apache License]&lt;br /&gt;
|http://www.openssl.org/&lt;br /&gt;
|Provides encryption for sensitive actions such as user login&lt;br /&gt;
|-&lt;br /&gt;
|sdl&lt;br /&gt;
|[https://github.com/secondlife/3p-sdl2 3p-sdl2]&lt;br /&gt;
|[https://github.com/libsdl-org/SDL/blob/main/LICENSE.txt zlib License]&lt;br /&gt;
|http://www.libsdl.org/index.php&lt;br /&gt;
|The Simple DirectMedia Layer libraries are used for handling input and basic window/GL setup on the Linux client.&lt;br /&gt;
|-&lt;br /&gt;
|slvoice&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|This is the Vivox provided executable (renamed SLVoice)&lt;br /&gt;
|-&lt;br /&gt;
|tut&lt;br /&gt;
|[https://github.com/secondlife/3p-tut 3p-tut]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-tut/blob/master/LICENSE.txt BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://tut-framework.sourceforge.net/&lt;br /&gt;
|Unit test framework based on the use of c++ meta template programming. Used to test LL libraries.&lt;br /&gt;
|-&lt;br /&gt;
|xmlrpc_epi&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi 3p-xmlrpc-epi]&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi/blob/master/xmlrpc-epi/COPYING Epinions, Inc License]&lt;br /&gt;
|http://xmlrpc-epi.sourceforge.net/&lt;br /&gt;
|XML-RPC protocol utility&lt;br /&gt;
|-&lt;br /&gt;
|zlib-ng&lt;br /&gt;
|[https://github.com/secondlife/3p-zlib-ng 3p-zlib-ng]&lt;br /&gt;
|[https://github.com/zlib-ng/zlib-ng/blob/develop/LICENSE.md zlib License]&lt;br /&gt;
|http://www.zlib.net/&lt;br /&gt;
|zlib is a lossless data-compression library which is used for a variety of network i/o and file compression.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Redistribution =&lt;br /&gt;
Some files in the SL installation can&#039;t be freely redistributed. This means it&#039;s not possible to simply package and offer for download a modified viewer: some files &#039;&#039;&#039;must not&#039;&#039;&#039; be redistributed. &lt;br /&gt;
&lt;br /&gt;
This page documents the licenses and redistribution requirements of various files used by the viewer.&lt;br /&gt;
&lt;br /&gt;
== Windows ==&lt;br /&gt;
These are the files that need to be shipped with the Windows version&lt;br /&gt;
&lt;br /&gt;
=== Runtime ===&lt;br /&gt;
Microsoft components. It&#039;s not yet known whether they&#039;re redistributable.&lt;br /&gt;
&lt;br /&gt;
* dbghelp.dll: [http://msdn2.microsoft.com/EN-US/library/ms679294.aspx Redistributable]&lt;br /&gt;
* msvcp71.dll: C++ Runtime Library, redistributable    -viewer now using msvcp80.dll when compiled with VC2005&lt;br /&gt;
* msvcr71.dll: C Runtime Library, [http://msdn2.microsoft.com/en-us/library/abx4dbyh(vs.71).aspx Redistributable]   -viewer now using msvcr80.dll when compiled with VC2005&lt;br /&gt;
&lt;br /&gt;
== FONTS ==&lt;br /&gt;
Meta font software files&lt;br /&gt;
&lt;br /&gt;
From the Readme.txt&lt;br /&gt;
 The Meta font software files contained in this folder are the&lt;br /&gt;
 copyrighted property of FSI FontShop International (&amp;quot;FSI&amp;quot;) and are&lt;br /&gt;
 licensed by FSI solely for use by Linden Research, Inc. and by&lt;br /&gt;
 residents or users of Second Life in the Second Life environment,&lt;br /&gt;
 subject to the Second Life Terms of Service.  These Meta font software&lt;br /&gt;
 files may not be copied by residents or developers of Second Life or&lt;br /&gt;
 used by them for any other purpose whatsoever.&lt;br /&gt;
&lt;br /&gt;
*  MtBkLfRg.ttf &lt;br /&gt;
*  MtBdLfRg.ttf&lt;br /&gt;
&lt;br /&gt;
NOTE:  These files must be replaced for an true opensource build&lt;br /&gt;
&lt;br /&gt;
=== Mozilla ===&lt;br /&gt;
The Gecko engine is MPL/GPL/LGPL licensed, which makes it redistributable.&lt;br /&gt;
&lt;br /&gt;
* app_settings/mozilla&lt;br /&gt;
* freebl3.dll&lt;br /&gt;
* gksvggdiplus.dll&lt;br /&gt;
* js3250.dll&lt;br /&gt;
* nspr4.dll&lt;br /&gt;
* nss3.dll&lt;br /&gt;
* nssckbi.dll&lt;br /&gt;
* plc4.dll,&lt;br /&gt;
* plds4.dll&lt;br /&gt;
* smime3.dll&lt;br /&gt;
* softokn3.dll&lt;br /&gt;
* ssl3.dll&lt;br /&gt;
* xpcom.dll&lt;br /&gt;
* xul.dll&lt;br /&gt;
&lt;br /&gt;
=== Kakadu ===&lt;br /&gt;
JPEG2000 library. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;.&lt;br /&gt;
OpenJPEG is an alternative library that is redistributable&lt;br /&gt;
&lt;br /&gt;
* llkdu.dll&lt;br /&gt;
&lt;br /&gt;
Starting with Viewer 2.5, llkdu.dll has been phased out. The libkdu.a and kdu.lib are still not redistributable but can be built from the toolkit provided by Kakadu under a license. How to build a viewer with such a library is documented in [[KDU Implementation]].&lt;br /&gt;
&lt;br /&gt;
=== OpenSSL ===&lt;br /&gt;
SSL library. Redistributable.&lt;br /&gt;
&lt;br /&gt;
* libeay32.dll&lt;br /&gt;
* ssleay32.dll&lt;br /&gt;
&lt;br /&gt;
=== Vivox ===&lt;br /&gt;
Vivox is the component that provides the voice functionality.&lt;br /&gt;
&lt;br /&gt;
* SLVoice.exe: Voice daemon. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* SLVoiceAgent: Exact purpose unknown. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* alut.dll: OpenAL, by Creative Labs. Redistributable.&lt;br /&gt;
* ortp.dll: oRTP project, LGPL. Redistributable.&lt;br /&gt;
* srtp.dll: Secure RTP?&lt;br /&gt;
* vivoxsdk.dll: Vivox Communications API. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* wrap_oal.dll: OpenAL32 by Creative Labs. Redistributable.&lt;br /&gt;
&lt;br /&gt;
=== Unknown Libraries ===&lt;br /&gt;
The purpose and licensing of these files is not known:&lt;br /&gt;
&lt;br /&gt;
* tntk.dll: Probably Vivox related  -see [https://wiki.secondlife.com/wiki/Voice/Technical Voice/Technical] for other info on ViVox files&lt;br /&gt;
&lt;br /&gt;
[[Category:Compiling viewer]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217091</id>
		<title>Third Party Libraries</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217091"/>
		<updated>2024-08-10T18:55:05Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Add release example image&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
The following table lists libraries and other third party (3p) dependencies used in constructing the Second Life Viewer.&lt;br /&gt;
&lt;br /&gt;
Most of these (all the open source ones) should be available as prebuilds packaged by and for use with [[Autobuild]].&lt;br /&gt;
&lt;br /&gt;
Anywhere that the following disagrees with the autobuild.xml file in the viewer source, that file is correct.&lt;br /&gt;
&lt;br /&gt;
=== Creating a new third party package ===&lt;br /&gt;
&lt;br /&gt;
Need to create a new third party package? The best route may be to start with an existing package similar to the one you wish to create and modify.&lt;br /&gt;
&lt;br /&gt;
==== Generic Layout ====&lt;br /&gt;
&lt;br /&gt;
3p packages should vendor upstream contents using [[https://git-scm.com/book/en/v2/Git-Tools-Submodules git submodules] unless a source git repository for the source dependency is not available.&lt;br /&gt;
A typical 3p package layout looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
.&lt;br /&gt;
├── [vendor submodule]/&lt;br /&gt;
├── .github/&lt;br /&gt;
├── autobuild.xml&lt;br /&gt;
├── build-cmd.sh&lt;br /&gt;
├── LICENSE&lt;br /&gt;
└── README.md&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Contributor Instructions ====&lt;br /&gt;
&lt;br /&gt;
The general workflow for contributing changes to a 3p library looks like this:&lt;br /&gt;
&lt;br /&gt;
# Fork the repository and make changes. Be sure to test your work locally.&lt;br /&gt;
# Create a PR (Be sure to follow [https://gist.github.com/mikepea/863f63d6e37281e329f8 PR Etiquette] and our [https://github.com/secondlife/.github/blob/main/CODE_OF_CONDUCT.md Code of Conduct])&lt;br /&gt;
# Have this PR reviewed and merged&lt;br /&gt;
# Done! The maintainer should cut a new version as appropriate using the workflow described below.&lt;br /&gt;
&lt;br /&gt;
==== Maintainer Instructions ====&lt;br /&gt;
&lt;br /&gt;
[[File:Screenshot 2024-08-10 at 11-48-23 Releases · secondlife 3p-zlib-ng.png|thumb|top|right|A properly versioned and promoted release of 3p-zlib-ng]]&lt;br /&gt;
&lt;br /&gt;
If you are a 3p package maintainer you will want to follow these instructions to review and release new versions:&lt;br /&gt;
&lt;br /&gt;
# Review PRs quickly, and merge them directly into the default branch once approved&lt;br /&gt;
# Cut a new release by using Github&#039;s Releases feature:&lt;br /&gt;
## Navigate to &#039;&#039;&#039;Repository -&amp;gt; Releases&#039;&#039;&#039; and click &#039;&#039;&#039;&amp;quot;Draft new Release&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
## Click &#039;&#039;&#039;Choose a tag&#039;&#039;&#039; and create a new tag using the format &amp;lt;code&amp;gt;v[vendor version]-[release num]&amp;lt;/code&amp;gt; described below&lt;br /&gt;
## Click &#039;&#039;&#039;Generate release notes&#039;&#039;&#039; and edit them as appropriate. Keep the title of the release equal to the version being used.&lt;br /&gt;
## Click &#039;&#039;Publish release&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
After the release is published, CI/CD should be triggered by the addition of the new tag, and provide a nice set of packages and instructions for consumers to use.&lt;br /&gt;
&lt;br /&gt;
===== Version format =====&lt;br /&gt;
&lt;br /&gt;
3p packages should be released using the following version schema:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Format&lt;br /&gt;
&lt;br /&gt;
  vUPSTREAM-rRELEASE&lt;br /&gt;
&lt;br /&gt;
  Example: v1.0.0-r2 (Second vendored release of v1.0.0 of this library)&lt;br /&gt;
&lt;br /&gt;
  UPSTREAM - Upstream package version. ex. 1.0.0 or 6.7&lt;br /&gt;
  RELEASE - Vendored release number, ex. 1, 2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== List of third party libraries ===&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;3&amp;quot;&lt;br /&gt;
|- style=&amp;quot;background:#ffdead;&amp;quot;&lt;br /&gt;
!Library&lt;br /&gt;
!Repository&lt;br /&gt;
!License&lt;br /&gt;
!Upstream Home&lt;br /&gt;
!Description&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|apr_suite&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/ 3p-apr]&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/blob/main/apr/LICENSE Apache License]&lt;br /&gt;
|http://apr.apache.org/&lt;br /&gt;
|&amp;quot;A set of C functions which provide a portable and consistent interface into operating system - or simply widely useful - functionality. We primarily use it for threading and socket i/o. This is actually three packages, apr, apr-util and apr-iconv.&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|boost&lt;br /&gt;
|[https://github.com/secondlife/3p-boost 3p-boost]&lt;br /&gt;
|[https://github.com/boostorg/boost/blob/master/LICENSE_1_0.txt Boost License]&lt;br /&gt;
|http://www.boost.org/&lt;br /&gt;
|A set of portable C++ libraries which provide a wide set of functionality. Used primarily for tokenization.&lt;br /&gt;
|-&lt;br /&gt;
|curl&lt;br /&gt;
|[https://github.com/secondlife/3p-curl 3p-curl]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-curl/blob/master/curl/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
| http://curl.haxx.se/libcurl/&lt;br /&gt;
|Handles moving data across the net in many different protocols. Used to GET/POST/PUT/DELETE web resources.&lt;br /&gt;
|-&lt;br /&gt;
|expat&lt;br /&gt;
|[https://github.com/secondlife/3p-expat 3p-expat]&lt;br /&gt;
|[https://github.com/libexpat/libexpat/blob/master/COPYING MIT License]&lt;br /&gt;
|https://github.com/libexpat/libexpat&lt;br /&gt;
|XML parser.&lt;br /&gt;
|-&lt;br /&gt;
|freetype&lt;br /&gt;
|[https://github.com/secondlife/3p-freetype 3p-freetype]&lt;br /&gt;
|[https://github.com/freetype/freetype/blob/master/LICENSE.TXT Freetype License and Others]&lt;br /&gt;
|http://www.freetype.org/&lt;br /&gt;
|Font engine.&lt;br /&gt;
|-&lt;br /&gt;
|glh_linear&lt;br /&gt;
|[https://github.com/secondlife/3p-glh_linear 3p-glh_linear]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|nVidia NVParse SDK: platform-independent C++ Apple OpenGL helper library&lt;br /&gt;
|-&lt;br /&gt;
|gstreamer&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-gstreamer 3p-gstreamer]&lt;br /&gt;
|Open&lt;br /&gt;
|http://gstreamer.freedesktop.org&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|havok&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|jpeglib&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-jpeglib 3p-jpeglib]&lt;br /&gt;
|Open&lt;br /&gt;
|http://www.ijg.org/&lt;br /&gt;
|JPEG decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|kdu&lt;br /&gt;
|[https://hg.lindenlab.com/lindenlab/3p-kdu-private 3p-kdu-private]&amp;lt;sup&amp;gt;[[#PrivateRepo|LL]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|Commercial&lt;br /&gt;
|http://www.kakadusoftware.com&lt;br /&gt;
|Kakadu (KDU) JPEG-2000 decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|libpng&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-libpng 3p-libpng]&lt;br /&gt;
|Open&lt;br /&gt;
| http://www.libpng.org/pub/png/libpng.html&lt;br /&gt;
|PNG image library.&lt;br /&gt;
|-&lt;br /&gt;
|libxml2&lt;br /&gt;
|[https://github.com/secondlife/3p-libxml2 3p-libxml2]&lt;br /&gt;
|[https://gitlab.gnome.org/GNOME/libxml2/-/blob/master/Copyright?ref_type=heads MIT License]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|ndofdev&lt;br /&gt;
|[https://github.com/secondlife/3p-libndofdev 3p-libndofdev]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-libndofdev/blob/master/libndofdev/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|Provides joystick driver support (mac and win) for the 3DConnexion SpaceNavigator.&lt;br /&gt;
|-&lt;br /&gt;
|ogg_vorbis&lt;br /&gt;
|[https://github.com/secondlife/3p-ogg_vorbis 3p-ogvorbis]&lt;br /&gt;
|&#039;&#039;[https://github.com/xiph/ogg/blob/master/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://xiph.org/ogg/&lt;br /&gt;
|Audio codec and playback.&lt;br /&gt;
|-&lt;br /&gt;
|openal&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft 3p-openal-soft]&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft/blob/main/openal-soft/COPYING GPL]&lt;br /&gt;
|http://connect.creativelabs.com/openal/default.aspx&lt;br /&gt;
|3D Spatial audio&lt;br /&gt;
|-&lt;br /&gt;
|openjpeg&lt;br /&gt;
|[https://github.com/secondlife/3p-openjpeg 3p-openjpeg]&lt;br /&gt;
|&#039;&#039;[https://github.com/uclouvain/openjpeg/blob/master/LICENSE BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://www.openjpeg.org/&lt;br /&gt;
|An open-source JPEG-2000 library; a slower alternative to Kadaku. Used in the open source release&lt;br /&gt;
|-&lt;br /&gt;
|openssl&lt;br /&gt;
|[https://github.com/secondlife/3p-openssl 3p-openssl]&lt;br /&gt;
|[https://github.com/openssl/openssl/blob/master/LICENSE.txt Apache License]&lt;br /&gt;
|http://www.openssl.org/&lt;br /&gt;
|Provides encryption for sensitive actions such as user login&lt;br /&gt;
|-&lt;br /&gt;
|sdl&lt;br /&gt;
|[https://github.com/secondlife/3p-sdl2 3p-sdl2]&lt;br /&gt;
|[https://github.com/libsdl-org/SDL/blob/main/LICENSE.txt zlib License]&lt;br /&gt;
|http://www.libsdl.org/index.php&lt;br /&gt;
|The Simple DirectMedia Layer libraries are used for handling input and basic window/GL setup on the Linux client.&lt;br /&gt;
|-&lt;br /&gt;
|slvoice&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|This is the Vivox provided executable (renamed SLVoice)&lt;br /&gt;
|-&lt;br /&gt;
|tut&lt;br /&gt;
|[https://github.com/secondlife/3p-tut 3p-tut]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-tut/blob/master/LICENSE.txt BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://tut-framework.sourceforge.net/&lt;br /&gt;
|Unit test framework based on the use of c++ meta template programming. Used to test LL libraries.&lt;br /&gt;
|-&lt;br /&gt;
|xmlrpc_epi&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi 3p-xmlrpc-epi]&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi/blob/master/xmlrpc-epi/COPYING Epinions, Inc License]&lt;br /&gt;
|http://xmlrpc-epi.sourceforge.net/&lt;br /&gt;
|XML-RPC protocol utility&lt;br /&gt;
|-&lt;br /&gt;
|zlib-ng&lt;br /&gt;
|[https://github.com/secondlife/3p-zlib-ng 3p-zlib-ng]&lt;br /&gt;
|[https://github.com/zlib-ng/zlib-ng/blob/develop/LICENSE.md zlib License]&lt;br /&gt;
|http://www.zlib.net/&lt;br /&gt;
|zlib is a lossless data-compression library which is used for a variety of network i/o and file compression.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Redistribution =&lt;br /&gt;
Some files in the SL installation can&#039;t be freely redistributed. This means it&#039;s not possible to simply package and offer for download a modified viewer: some files &#039;&#039;&#039;must not&#039;&#039;&#039; be redistributed. &lt;br /&gt;
&lt;br /&gt;
This page documents the licenses and redistribution requirements of various files used by the viewer.&lt;br /&gt;
&lt;br /&gt;
== Windows ==&lt;br /&gt;
These are the files that need to be shipped with the Windows version&lt;br /&gt;
&lt;br /&gt;
=== Runtime ===&lt;br /&gt;
Microsoft components. It&#039;s not yet known whether they&#039;re redistributable.&lt;br /&gt;
&lt;br /&gt;
* dbghelp.dll: [http://msdn2.microsoft.com/EN-US/library/ms679294.aspx Redistributable]&lt;br /&gt;
* msvcp71.dll: C++ Runtime Library, redistributable    -viewer now using msvcp80.dll when compiled with VC2005&lt;br /&gt;
* msvcr71.dll: C Runtime Library, [http://msdn2.microsoft.com/en-us/library/abx4dbyh(vs.71).aspx Redistributable]   -viewer now using msvcr80.dll when compiled with VC2005&lt;br /&gt;
&lt;br /&gt;
== FONTS ==&lt;br /&gt;
Meta font software files&lt;br /&gt;
&lt;br /&gt;
From the Readme.txt&lt;br /&gt;
 The Meta font software files contained in this folder are the&lt;br /&gt;
 copyrighted property of FSI FontShop International (&amp;quot;FSI&amp;quot;) and are&lt;br /&gt;
 licensed by FSI solely for use by Linden Research, Inc. and by&lt;br /&gt;
 residents or users of Second Life in the Second Life environment,&lt;br /&gt;
 subject to the Second Life Terms of Service.  These Meta font software&lt;br /&gt;
 files may not be copied by residents or developers of Second Life or&lt;br /&gt;
 used by them for any other purpose whatsoever.&lt;br /&gt;
&lt;br /&gt;
*  MtBkLfRg.ttf &lt;br /&gt;
*  MtBdLfRg.ttf&lt;br /&gt;
&lt;br /&gt;
NOTE:  These files must be replaced for an true opensource build&lt;br /&gt;
&lt;br /&gt;
=== Mozilla ===&lt;br /&gt;
The Gecko engine is MPL/GPL/LGPL licensed, which makes it redistributable.&lt;br /&gt;
&lt;br /&gt;
* app_settings/mozilla&lt;br /&gt;
* freebl3.dll&lt;br /&gt;
* gksvggdiplus.dll&lt;br /&gt;
* js3250.dll&lt;br /&gt;
* nspr4.dll&lt;br /&gt;
* nss3.dll&lt;br /&gt;
* nssckbi.dll&lt;br /&gt;
* plc4.dll,&lt;br /&gt;
* plds4.dll&lt;br /&gt;
* smime3.dll&lt;br /&gt;
* softokn3.dll&lt;br /&gt;
* ssl3.dll&lt;br /&gt;
* xpcom.dll&lt;br /&gt;
* xul.dll&lt;br /&gt;
&lt;br /&gt;
=== Kakadu ===&lt;br /&gt;
JPEG2000 library. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;.&lt;br /&gt;
OpenJPEG is an alternative library that is redistributable&lt;br /&gt;
&lt;br /&gt;
* llkdu.dll&lt;br /&gt;
&lt;br /&gt;
Starting with Viewer 2.5, llkdu.dll has been phased out. The libkdu.a and kdu.lib are still not redistributable but can be built from the toolkit provided by Kakadu under a license. How to build a viewer with such a library is documented in [[KDU Implementation]].&lt;br /&gt;
&lt;br /&gt;
=== OpenSSL ===&lt;br /&gt;
SSL library. Redistributable.&lt;br /&gt;
&lt;br /&gt;
* libeay32.dll&lt;br /&gt;
* ssleay32.dll&lt;br /&gt;
&lt;br /&gt;
=== Vivox ===&lt;br /&gt;
Vivox is the component that provides the voice functionality.&lt;br /&gt;
&lt;br /&gt;
* SLVoice.exe: Voice daemon. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* SLVoiceAgent: Exact purpose unknown. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* alut.dll: OpenAL, by Creative Labs. Redistributable.&lt;br /&gt;
* ortp.dll: oRTP project, LGPL. Redistributable.&lt;br /&gt;
* srtp.dll: Secure RTP?&lt;br /&gt;
* vivoxsdk.dll: Vivox Communications API. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* wrap_oal.dll: OpenAL32 by Creative Labs. Redistributable.&lt;br /&gt;
&lt;br /&gt;
=== Unknown Libraries ===&lt;br /&gt;
The purpose and licensing of these files is not known:&lt;br /&gt;
&lt;br /&gt;
* tntk.dll: Probably Vivox related  -see [https://wiki.secondlife.com/wiki/Voice/Technical Voice/Technical] for other info on ViVox files&lt;br /&gt;
&lt;br /&gt;
[[Category:Compiling viewer]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217090</id>
		<title>Third Party Libraries</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217090"/>
		<updated>2024-08-10T18:50:54Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
The following table lists libraries and other third party (3p) dependencies used in constructing the Second Life Viewer.&lt;br /&gt;
&lt;br /&gt;
Most of these (all the open source ones) should be available as prebuilds packaged by and for use with [[Autobuild]].&lt;br /&gt;
&lt;br /&gt;
Anywhere that the following disagrees with the autobuild.xml file in the viewer source, that file is correct.&lt;br /&gt;
&lt;br /&gt;
=== Creating a new third party package ===&lt;br /&gt;
&lt;br /&gt;
Need to create a new third party package? The best route may be to start with an existing package similar to the one you wish to create and modify.&lt;br /&gt;
&lt;br /&gt;
==== Generic Layout ====&lt;br /&gt;
&lt;br /&gt;
3p packages should vendor upstream contents using [[https://git-scm.com/book/en/v2/Git-Tools-Submodules git submodules] unless a source git repository for the source dependency is not available.&lt;br /&gt;
A typical 3p package layout looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
.&lt;br /&gt;
├── [vendor submodule]/&lt;br /&gt;
├── .github/&lt;br /&gt;
├── autobuild.xml&lt;br /&gt;
├── build-cmd.sh&lt;br /&gt;
├── LICENSE&lt;br /&gt;
└── README.md&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Contributor Instructions ====&lt;br /&gt;
&lt;br /&gt;
The general workflow for contributing changes to a 3p library looks like this:&lt;br /&gt;
&lt;br /&gt;
# Fork the repository and make changes. Be sure to test your work locally.&lt;br /&gt;
# Create a PR (Be sure to follow [https://gist.github.com/mikepea/863f63d6e37281e329f8 PR Etiquette] and our [https://github.com/secondlife/.github/blob/main/CODE_OF_CONDUCT.md Code of Conduct])&lt;br /&gt;
# Have this PR reviewed and merged&lt;br /&gt;
# Done! The maintainer should cut a new version as appropriate using the workflow described below.&lt;br /&gt;
&lt;br /&gt;
==== Maintainer Instructions ====&lt;br /&gt;
&lt;br /&gt;
If you are a 3p package maintainer you will want to follow these instructions to review and release new versions:&lt;br /&gt;
&lt;br /&gt;
# Review PRs quickly, and merge them directly into the default branch once approved&lt;br /&gt;
# Cut a new release by using Github&#039;s Releases feature:&lt;br /&gt;
## Navigate to &#039;&#039;&#039;Repository -&amp;gt; Releases&#039;&#039;&#039; and click &#039;&#039;&#039;&amp;quot;Draft new Release&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
## Click &#039;&#039;&#039;Choose a tag&#039;&#039;&#039; and create a new tag using the format &amp;lt;code&amp;gt;v[vendor version]-[release num]&amp;lt;/code&amp;gt; described below&lt;br /&gt;
## Click &#039;&#039;&#039;Generate release notes&#039;&#039;&#039; and edit them as appropriate. Keep the title of the release equal to the version being used.&lt;br /&gt;
## Click &#039;&#039;Publish release&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
After the release is published, CI/CD should be triggered by the addition of the new tag, and provide a nice set of packages and instructions for consumers to use:&lt;br /&gt;
&lt;br /&gt;
[[File:Screenshot 2024-08-10 at 11-48-23 Releases · secondlife 3p-zlib-ng.png|300px]]&lt;br /&gt;
&lt;br /&gt;
===== Version format =====&lt;br /&gt;
&lt;br /&gt;
3p packages should be released using the following version schema:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Format&lt;br /&gt;
&lt;br /&gt;
  vUPSTREAM-rRELEASE&lt;br /&gt;
&lt;br /&gt;
  Example: v1.0.0-r2 (Second vendored release of v1.0.0 of this library)&lt;br /&gt;
&lt;br /&gt;
  UPSTREAM - Upstream package version. ex. 1.0.0 or 6.7&lt;br /&gt;
  RELEASE - Vendored release number, ex. 1, 2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== List of third party libraries ===&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;3&amp;quot;&lt;br /&gt;
|- style=&amp;quot;background:#ffdead;&amp;quot;&lt;br /&gt;
!Library&lt;br /&gt;
!Repository&lt;br /&gt;
!License&lt;br /&gt;
!Upstream Home&lt;br /&gt;
!Description&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|apr_suite&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/ 3p-apr]&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/blob/main/apr/LICENSE Apache License]&lt;br /&gt;
|http://apr.apache.org/&lt;br /&gt;
|&amp;quot;A set of C functions which provide a portable and consistent interface into operating system - or simply widely useful - functionality. We primarily use it for threading and socket i/o. This is actually three packages, apr, apr-util and apr-iconv.&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|boost&lt;br /&gt;
|[https://github.com/secondlife/3p-boost 3p-boost]&lt;br /&gt;
|[https://github.com/boostorg/boost/blob/master/LICENSE_1_0.txt Boost License]&lt;br /&gt;
|http://www.boost.org/&lt;br /&gt;
|A set of portable C++ libraries which provide a wide set of functionality. Used primarily for tokenization.&lt;br /&gt;
|-&lt;br /&gt;
|curl&lt;br /&gt;
|[https://github.com/secondlife/3p-curl 3p-curl]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-curl/blob/master/curl/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
| http://curl.haxx.se/libcurl/&lt;br /&gt;
|Handles moving data across the net in many different protocols. Used to GET/POST/PUT/DELETE web resources.&lt;br /&gt;
|-&lt;br /&gt;
|expat&lt;br /&gt;
|[https://github.com/secondlife/3p-expat 3p-expat]&lt;br /&gt;
|[https://github.com/libexpat/libexpat/blob/master/COPYING MIT License]&lt;br /&gt;
|https://github.com/libexpat/libexpat&lt;br /&gt;
|XML parser.&lt;br /&gt;
|-&lt;br /&gt;
|freetype&lt;br /&gt;
|[https://github.com/secondlife/3p-freetype 3p-freetype]&lt;br /&gt;
|[https://github.com/freetype/freetype/blob/master/LICENSE.TXT Freetype License and Others]&lt;br /&gt;
|http://www.freetype.org/&lt;br /&gt;
|Font engine.&lt;br /&gt;
|-&lt;br /&gt;
|glh_linear&lt;br /&gt;
|[https://github.com/secondlife/3p-glh_linear 3p-glh_linear]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|nVidia NVParse SDK: platform-independent C++ Apple OpenGL helper library&lt;br /&gt;
|-&lt;br /&gt;
|gstreamer&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-gstreamer 3p-gstreamer]&lt;br /&gt;
|Open&lt;br /&gt;
|http://gstreamer.freedesktop.org&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|havok&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|jpeglib&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-jpeglib 3p-jpeglib]&lt;br /&gt;
|Open&lt;br /&gt;
|http://www.ijg.org/&lt;br /&gt;
|JPEG decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|kdu&lt;br /&gt;
|[https://hg.lindenlab.com/lindenlab/3p-kdu-private 3p-kdu-private]&amp;lt;sup&amp;gt;[[#PrivateRepo|LL]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|Commercial&lt;br /&gt;
|http://www.kakadusoftware.com&lt;br /&gt;
|Kakadu (KDU) JPEG-2000 decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|libpng&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-libpng 3p-libpng]&lt;br /&gt;
|Open&lt;br /&gt;
| http://www.libpng.org/pub/png/libpng.html&lt;br /&gt;
|PNG image library.&lt;br /&gt;
|-&lt;br /&gt;
|libxml2&lt;br /&gt;
|[https://github.com/secondlife/3p-libxml2 3p-libxml2]&lt;br /&gt;
|[https://gitlab.gnome.org/GNOME/libxml2/-/blob/master/Copyright?ref_type=heads MIT License]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|ndofdev&lt;br /&gt;
|[https://github.com/secondlife/3p-libndofdev 3p-libndofdev]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-libndofdev/blob/master/libndofdev/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|Provides joystick driver support (mac and win) for the 3DConnexion SpaceNavigator.&lt;br /&gt;
|-&lt;br /&gt;
|ogg_vorbis&lt;br /&gt;
|[https://github.com/secondlife/3p-ogg_vorbis 3p-ogvorbis]&lt;br /&gt;
|&#039;&#039;[https://github.com/xiph/ogg/blob/master/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://xiph.org/ogg/&lt;br /&gt;
|Audio codec and playback.&lt;br /&gt;
|-&lt;br /&gt;
|openal&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft 3p-openal-soft]&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft/blob/main/openal-soft/COPYING GPL]&lt;br /&gt;
|http://connect.creativelabs.com/openal/default.aspx&lt;br /&gt;
|3D Spatial audio&lt;br /&gt;
|-&lt;br /&gt;
|openjpeg&lt;br /&gt;
|[https://github.com/secondlife/3p-openjpeg 3p-openjpeg]&lt;br /&gt;
|&#039;&#039;[https://github.com/uclouvain/openjpeg/blob/master/LICENSE BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://www.openjpeg.org/&lt;br /&gt;
|An open-source JPEG-2000 library; a slower alternative to Kadaku. Used in the open source release&lt;br /&gt;
|-&lt;br /&gt;
|openssl&lt;br /&gt;
|[https://github.com/secondlife/3p-openssl 3p-openssl]&lt;br /&gt;
|[https://github.com/openssl/openssl/blob/master/LICENSE.txt Apache License]&lt;br /&gt;
|http://www.openssl.org/&lt;br /&gt;
|Provides encryption for sensitive actions such as user login&lt;br /&gt;
|-&lt;br /&gt;
|sdl&lt;br /&gt;
|[https://github.com/secondlife/3p-sdl2 3p-sdl2]&lt;br /&gt;
|[https://github.com/libsdl-org/SDL/blob/main/LICENSE.txt zlib License]&lt;br /&gt;
|http://www.libsdl.org/index.php&lt;br /&gt;
|The Simple DirectMedia Layer libraries are used for handling input and basic window/GL setup on the Linux client.&lt;br /&gt;
|-&lt;br /&gt;
|slvoice&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|This is the Vivox provided executable (renamed SLVoice)&lt;br /&gt;
|-&lt;br /&gt;
|tut&lt;br /&gt;
|[https://github.com/secondlife/3p-tut 3p-tut]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-tut/blob/master/LICENSE.txt BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://tut-framework.sourceforge.net/&lt;br /&gt;
|Unit test framework based on the use of c++ meta template programming. Used to test LL libraries.&lt;br /&gt;
|-&lt;br /&gt;
|xmlrpc_epi&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi 3p-xmlrpc-epi]&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi/blob/master/xmlrpc-epi/COPYING Epinions, Inc License]&lt;br /&gt;
|http://xmlrpc-epi.sourceforge.net/&lt;br /&gt;
|XML-RPC protocol utility&lt;br /&gt;
|-&lt;br /&gt;
|zlib-ng&lt;br /&gt;
|[https://github.com/secondlife/3p-zlib-ng 3p-zlib-ng]&lt;br /&gt;
|[https://github.com/zlib-ng/zlib-ng/blob/develop/LICENSE.md zlib License]&lt;br /&gt;
|http://www.zlib.net/&lt;br /&gt;
|zlib is a lossless data-compression library which is used for a variety of network i/o and file compression.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Redistribution =&lt;br /&gt;
Some files in the SL installation can&#039;t be freely redistributed. This means it&#039;s not possible to simply package and offer for download a modified viewer: some files &#039;&#039;&#039;must not&#039;&#039;&#039; be redistributed. &lt;br /&gt;
&lt;br /&gt;
This page documents the licenses and redistribution requirements of various files used by the viewer.&lt;br /&gt;
&lt;br /&gt;
== Windows ==&lt;br /&gt;
These are the files that need to be shipped with the Windows version&lt;br /&gt;
&lt;br /&gt;
=== Runtime ===&lt;br /&gt;
Microsoft components. It&#039;s not yet known whether they&#039;re redistributable.&lt;br /&gt;
&lt;br /&gt;
* dbghelp.dll: [http://msdn2.microsoft.com/EN-US/library/ms679294.aspx Redistributable]&lt;br /&gt;
* msvcp71.dll: C++ Runtime Library, redistributable    -viewer now using msvcp80.dll when compiled with VC2005&lt;br /&gt;
* msvcr71.dll: C Runtime Library, [http://msdn2.microsoft.com/en-us/library/abx4dbyh(vs.71).aspx Redistributable]   -viewer now using msvcr80.dll when compiled with VC2005&lt;br /&gt;
&lt;br /&gt;
== FONTS ==&lt;br /&gt;
Meta font software files&lt;br /&gt;
&lt;br /&gt;
From the Readme.txt&lt;br /&gt;
 The Meta font software files contained in this folder are the&lt;br /&gt;
 copyrighted property of FSI FontShop International (&amp;quot;FSI&amp;quot;) and are&lt;br /&gt;
 licensed by FSI solely for use by Linden Research, Inc. and by&lt;br /&gt;
 residents or users of Second Life in the Second Life environment,&lt;br /&gt;
 subject to the Second Life Terms of Service.  These Meta font software&lt;br /&gt;
 files may not be copied by residents or developers of Second Life or&lt;br /&gt;
 used by them for any other purpose whatsoever.&lt;br /&gt;
&lt;br /&gt;
*  MtBkLfRg.ttf &lt;br /&gt;
*  MtBdLfRg.ttf&lt;br /&gt;
&lt;br /&gt;
NOTE:  These files must be replaced for an true opensource build&lt;br /&gt;
&lt;br /&gt;
=== Mozilla ===&lt;br /&gt;
The Gecko engine is MPL/GPL/LGPL licensed, which makes it redistributable.&lt;br /&gt;
&lt;br /&gt;
* app_settings/mozilla&lt;br /&gt;
* freebl3.dll&lt;br /&gt;
* gksvggdiplus.dll&lt;br /&gt;
* js3250.dll&lt;br /&gt;
* nspr4.dll&lt;br /&gt;
* nss3.dll&lt;br /&gt;
* nssckbi.dll&lt;br /&gt;
* plc4.dll,&lt;br /&gt;
* plds4.dll&lt;br /&gt;
* smime3.dll&lt;br /&gt;
* softokn3.dll&lt;br /&gt;
* ssl3.dll&lt;br /&gt;
* xpcom.dll&lt;br /&gt;
* xul.dll&lt;br /&gt;
&lt;br /&gt;
=== Kakadu ===&lt;br /&gt;
JPEG2000 library. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;.&lt;br /&gt;
OpenJPEG is an alternative library that is redistributable&lt;br /&gt;
&lt;br /&gt;
* llkdu.dll&lt;br /&gt;
&lt;br /&gt;
Starting with Viewer 2.5, llkdu.dll has been phased out. The libkdu.a and kdu.lib are still not redistributable but can be built from the toolkit provided by Kakadu under a license. How to build a viewer with such a library is documented in [[KDU Implementation]].&lt;br /&gt;
&lt;br /&gt;
=== OpenSSL ===&lt;br /&gt;
SSL library. Redistributable.&lt;br /&gt;
&lt;br /&gt;
* libeay32.dll&lt;br /&gt;
* ssleay32.dll&lt;br /&gt;
&lt;br /&gt;
=== Vivox ===&lt;br /&gt;
Vivox is the component that provides the voice functionality.&lt;br /&gt;
&lt;br /&gt;
* SLVoice.exe: Voice daemon. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* SLVoiceAgent: Exact purpose unknown. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* alut.dll: OpenAL, by Creative Labs. Redistributable.&lt;br /&gt;
* ortp.dll: oRTP project, LGPL. Redistributable.&lt;br /&gt;
* srtp.dll: Secure RTP?&lt;br /&gt;
* vivoxsdk.dll: Vivox Communications API. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* wrap_oal.dll: OpenAL32 by Creative Labs. Redistributable.&lt;br /&gt;
&lt;br /&gt;
=== Unknown Libraries ===&lt;br /&gt;
The purpose and licensing of these files is not known:&lt;br /&gt;
&lt;br /&gt;
* tntk.dll: Probably Vivox related  -see [https://wiki.secondlife.com/wiki/Voice/Technical Voice/Technical] for other info on ViVox files&lt;br /&gt;
&lt;br /&gt;
[[Category:Compiling viewer]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=File:Screenshot_2024-08-10_at_11-48-23_Releases_%C2%B7_secondlife_3p-zlib-ng.png&amp;diff=1217089</id>
		<title>File:Screenshot 2024-08-10 at 11-48-23 Releases · secondlife 3p-zlib-ng.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=File:Screenshot_2024-08-10_at_11-48-23_Releases_%C2%B7_secondlife_3p-zlib-ng.png&amp;diff=1217089"/>
		<updated>2024-08-10T18:49:49Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217088</id>
		<title>Third Party Libraries</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Third_Party_Libraries&amp;diff=1217088"/>
		<updated>2024-08-10T18:25:52Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Major update and clean up --updated all links, purged unused libraries.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
The following table lists libraries and other third party dependencies used in constructing the Second Life Viewer.&lt;br /&gt;
&lt;br /&gt;
Most of these (all the open source ones) should be available as prebuilds packaged by and for use with [[Autobuild]].&lt;br /&gt;
&lt;br /&gt;
Anywhere that the following disagrees with the autobuild.xml file in the viewer source, that file is correct.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Creating a new third party package&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;List of third party libraries&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;3&amp;quot;&lt;br /&gt;
|- style=&amp;quot;background:#ffdead;&amp;quot;&lt;br /&gt;
!Library&lt;br /&gt;
!Repository&lt;br /&gt;
!License&lt;br /&gt;
!Upstream Home&lt;br /&gt;
!Description&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|apr_suite&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/ 3p-apr]&lt;br /&gt;
|[https://github.com/secondlife/3p-apr_suite/blob/main/apr/LICENSE Apache License]&lt;br /&gt;
|http://apr.apache.org/&lt;br /&gt;
|&amp;quot;A set of C functions which provide a portable and consistent interface into operating system - or simply widely useful - functionality. We primarily use it for threading and socket i/o. This is actually three packages, apr, apr-util and apr-iconv.&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|boost&lt;br /&gt;
|[https://github.com/secondlife/3p-boost 3p-boost]&lt;br /&gt;
|[https://github.com/boostorg/boost/blob/master/LICENSE_1_0.txt Boost License]&lt;br /&gt;
|http://www.boost.org/&lt;br /&gt;
|A set of portable C++ libraries which provide a wide set of functionality. Used primarily for tokenization.&lt;br /&gt;
|-&lt;br /&gt;
|curl&lt;br /&gt;
|[https://github.com/secondlife/3p-curl 3p-curl]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-curl/blob/master/curl/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
| http://curl.haxx.se/libcurl/&lt;br /&gt;
|Handles moving data across the net in many different protocols. Used to GET/POST/PUT/DELETE web resources.&lt;br /&gt;
|-&lt;br /&gt;
|expat&lt;br /&gt;
|[https://github.com/secondlife/3p-expat 3p-expat]&lt;br /&gt;
|[https://github.com/libexpat/libexpat/blob/master/COPYING MIT License]&lt;br /&gt;
|https://github.com/libexpat/libexpat&lt;br /&gt;
|XML parser.&lt;br /&gt;
|-&lt;br /&gt;
|freetype&lt;br /&gt;
|[https://github.com/secondlife/3p-freetype 3p-freetype]&lt;br /&gt;
|[https://github.com/freetype/freetype/blob/master/LICENSE.TXT Freetype License and Others]&lt;br /&gt;
|http://www.freetype.org/&lt;br /&gt;
|Font engine.&lt;br /&gt;
|-&lt;br /&gt;
|glh_linear&lt;br /&gt;
|[https://github.com/secondlife/3p-glh_linear 3p-glh_linear]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|nVidia NVParse SDK: platform-independent C++ Apple OpenGL helper library&lt;br /&gt;
|-&lt;br /&gt;
|gstreamer&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-gstreamer 3p-gstreamer]&lt;br /&gt;
|Open&lt;br /&gt;
|http://gstreamer.freedesktop.org&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|havok&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|jpeglib&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-jpeglib 3p-jpeglib]&lt;br /&gt;
|Open&lt;br /&gt;
|http://www.ijg.org/&lt;br /&gt;
|JPEG decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|kdu&lt;br /&gt;
|[https://hg.lindenlab.com/lindenlab/3p-kdu-private 3p-kdu-private]&amp;lt;sup&amp;gt;[[#PrivateRepo|LL]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|Commercial&lt;br /&gt;
|http://www.kakadusoftware.com&lt;br /&gt;
|Kakadu (KDU) JPEG-2000 decoder library.&lt;br /&gt;
|-&lt;br /&gt;
|libpng&lt;br /&gt;
|[https://bitbucket.org/lindenlab/3p-libpng 3p-libpng]&lt;br /&gt;
|Open&lt;br /&gt;
| http://www.libpng.org/pub/png/libpng.html&lt;br /&gt;
|PNG image library.&lt;br /&gt;
|-&lt;br /&gt;
|libxml2&lt;br /&gt;
|[https://github.com/secondlife/3p-libxml2 3p-libxml2]&lt;br /&gt;
|[https://gitlab.gnome.org/GNOME/libxml2/-/blob/master/Copyright?ref_type=heads MIT License]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|ndofdev&lt;br /&gt;
|[https://github.com/secondlife/3p-libndofdev 3p-libndofdev]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-libndofdev/blob/master/libndofdev/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|Provides joystick driver support (mac and win) for the 3DConnexion SpaceNavigator.&lt;br /&gt;
|-&lt;br /&gt;
|ogg_vorbis&lt;br /&gt;
|[https://github.com/secondlife/3p-ogg_vorbis 3p-ogvorbis]&lt;br /&gt;
|&#039;&#039;[https://github.com/xiph/ogg/blob/master/COPYING BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://xiph.org/ogg/&lt;br /&gt;
|Audio codec and playback.&lt;br /&gt;
|-&lt;br /&gt;
|openal&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft 3p-openal-soft]&lt;br /&gt;
|[https://github.com/secondlife/3p-openal-soft/blob/main/openal-soft/COPYING GPL]&lt;br /&gt;
|http://connect.creativelabs.com/openal/default.aspx&lt;br /&gt;
|3D Spatial audio&lt;br /&gt;
|-&lt;br /&gt;
|openjpeg&lt;br /&gt;
|[https://github.com/secondlife/3p-openjpeg 3p-openjpeg]&lt;br /&gt;
|&#039;&#039;[https://github.com/uclouvain/openjpeg/blob/master/LICENSE BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://www.openjpeg.org/&lt;br /&gt;
|An open-source JPEG-2000 library; a slower alternative to Kadaku. Used in the open source release&lt;br /&gt;
|-&lt;br /&gt;
|openssl&lt;br /&gt;
|[https://github.com/secondlife/3p-openssl 3p-openssl]&lt;br /&gt;
|[https://github.com/openssl/openssl/blob/master/LICENSE.txt Apache License]&lt;br /&gt;
|http://www.openssl.org/&lt;br /&gt;
|Provides encryption for sensitive actions such as user login&lt;br /&gt;
|-&lt;br /&gt;
|sdl&lt;br /&gt;
|[https://github.com/secondlife/3p-sdl2 3p-sdl2]&lt;br /&gt;
|[https://github.com/libsdl-org/SDL/blob/main/LICENSE.txt zlib License]&lt;br /&gt;
|http://www.libsdl.org/index.php&lt;br /&gt;
|The Simple DirectMedia Layer libraries are used for handling input and basic window/GL setup on the Linux client.&lt;br /&gt;
|-&lt;br /&gt;
|slvoice&lt;br /&gt;
|&lt;br /&gt;
|Commercial&lt;br /&gt;
|&lt;br /&gt;
|This is the Vivox provided executable (renamed SLVoice)&lt;br /&gt;
|-&lt;br /&gt;
|tut&lt;br /&gt;
|[https://github.com/secondlife/3p-tut 3p-tut]&lt;br /&gt;
|&#039;&#039;[https://github.com/secondlife/3p-tut/blob/master/LICENSE.txt BSD-style]&#039;&#039;&amp;lt;sup&amp;gt;[[#BSDstyle|B]]&amp;lt;/sup&amp;gt;&lt;br /&gt;
|http://tut-framework.sourceforge.net/&lt;br /&gt;
|Unit test framework based on the use of c++ meta template programming. Used to test LL libraries.&lt;br /&gt;
|-&lt;br /&gt;
|xmlrpc_epi&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi 3p-xmlrpc-epi]&lt;br /&gt;
|[https://github.com/secondlife/3p-xmlrpc-epi/blob/master/xmlrpc-epi/COPYING Epinions, Inc License]&lt;br /&gt;
|http://xmlrpc-epi.sourceforge.net/&lt;br /&gt;
|XML-RPC protocol utility&lt;br /&gt;
|-&lt;br /&gt;
|zlib-ng&lt;br /&gt;
|[https://github.com/secondlife/3p-zlib-ng 3p-zlib-ng]&lt;br /&gt;
|[https://github.com/zlib-ng/zlib-ng/blob/develop/LICENSE.md zlib License]&lt;br /&gt;
|http://www.zlib.net/&lt;br /&gt;
|zlib is a lossless data-compression library which is used for a variety of network i/o and file compression.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Redistribution =&lt;br /&gt;
Some files in the SL installation can&#039;t be freely redistributed. This means it&#039;s not possible to simply package and offer for download a modified viewer: some files &#039;&#039;&#039;must not&#039;&#039;&#039; be redistributed. &lt;br /&gt;
&lt;br /&gt;
This page documents the licenses and redistribution requirements of various files used by the viewer.&lt;br /&gt;
&lt;br /&gt;
== Windows ==&lt;br /&gt;
These are the files that need to be shipped with the Windows version&lt;br /&gt;
&lt;br /&gt;
=== Runtime ===&lt;br /&gt;
Microsoft components. It&#039;s not yet known whether they&#039;re redistributable.&lt;br /&gt;
&lt;br /&gt;
* dbghelp.dll: [http://msdn2.microsoft.com/EN-US/library/ms679294.aspx Redistributable]&lt;br /&gt;
* msvcp71.dll: C++ Runtime Library, redistributable    -viewer now using msvcp80.dll when compiled with VC2005&lt;br /&gt;
* msvcr71.dll: C Runtime Library, [http://msdn2.microsoft.com/en-us/library/abx4dbyh(vs.71).aspx Redistributable]   -viewer now using msvcr80.dll when compiled with VC2005&lt;br /&gt;
&lt;br /&gt;
== FONTS ==&lt;br /&gt;
Meta font software files&lt;br /&gt;
&lt;br /&gt;
From the Readme.txt&lt;br /&gt;
 The Meta font software files contained in this folder are the&lt;br /&gt;
 copyrighted property of FSI FontShop International (&amp;quot;FSI&amp;quot;) and are&lt;br /&gt;
 licensed by FSI solely for use by Linden Research, Inc. and by&lt;br /&gt;
 residents or users of Second Life in the Second Life environment,&lt;br /&gt;
 subject to the Second Life Terms of Service.  These Meta font software&lt;br /&gt;
 files may not be copied by residents or developers of Second Life or&lt;br /&gt;
 used by them for any other purpose whatsoever.&lt;br /&gt;
&lt;br /&gt;
*  MtBkLfRg.ttf &lt;br /&gt;
*  MtBdLfRg.ttf&lt;br /&gt;
&lt;br /&gt;
NOTE:  These files must be replaced for an true opensource build&lt;br /&gt;
&lt;br /&gt;
=== Mozilla ===&lt;br /&gt;
The Gecko engine is MPL/GPL/LGPL licensed, which makes it redistributable.&lt;br /&gt;
&lt;br /&gt;
* app_settings/mozilla&lt;br /&gt;
* freebl3.dll&lt;br /&gt;
* gksvggdiplus.dll&lt;br /&gt;
* js3250.dll&lt;br /&gt;
* nspr4.dll&lt;br /&gt;
* nss3.dll&lt;br /&gt;
* nssckbi.dll&lt;br /&gt;
* plc4.dll,&lt;br /&gt;
* plds4.dll&lt;br /&gt;
* smime3.dll&lt;br /&gt;
* softokn3.dll&lt;br /&gt;
* ssl3.dll&lt;br /&gt;
* xpcom.dll&lt;br /&gt;
* xul.dll&lt;br /&gt;
&lt;br /&gt;
=== Kakadu ===&lt;br /&gt;
JPEG2000 library. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;.&lt;br /&gt;
OpenJPEG is an alternative library that is redistributable&lt;br /&gt;
&lt;br /&gt;
* llkdu.dll&lt;br /&gt;
&lt;br /&gt;
Starting with Viewer 2.5, llkdu.dll has been phased out. The libkdu.a and kdu.lib are still not redistributable but can be built from the toolkit provided by Kakadu under a license. How to build a viewer with such a library is documented in [[KDU Implementation]].&lt;br /&gt;
&lt;br /&gt;
=== OpenSSL ===&lt;br /&gt;
SSL library. Redistributable.&lt;br /&gt;
&lt;br /&gt;
* libeay32.dll&lt;br /&gt;
* ssleay32.dll&lt;br /&gt;
&lt;br /&gt;
=== Vivox ===&lt;br /&gt;
Vivox is the component that provides the voice functionality.&lt;br /&gt;
&lt;br /&gt;
* SLVoice.exe: Voice daemon. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* SLVoiceAgent: Exact purpose unknown. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* alut.dll: OpenAL, by Creative Labs. Redistributable.&lt;br /&gt;
* ortp.dll: oRTP project, LGPL. Redistributable.&lt;br /&gt;
* srtp.dll: Secure RTP?&lt;br /&gt;
* vivoxsdk.dll: Vivox Communications API. &#039;&#039;&#039;Not redistributable&#039;&#039;&#039;&lt;br /&gt;
* wrap_oal.dll: OpenAL32 by Creative Labs. Redistributable.&lt;br /&gt;
&lt;br /&gt;
=== Unknown Libraries ===&lt;br /&gt;
The purpose and licensing of these files is not known:&lt;br /&gt;
&lt;br /&gt;
* tntk.dll: Probably Vivox related  -see [https://wiki.secondlife.com/wiki/Voice/Technical Voice/Technical] for other info on ViVox files&lt;br /&gt;
&lt;br /&gt;
[[Category:Compiling viewer]]&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1217019</id>
		<title>Lua FAQ</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1217019"/>
		<updated>2024-08-07T05:50:01Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
=== Can I keep using LSL? ===&lt;br /&gt;
&lt;br /&gt;
Yep! LSL should work exactly the same as before, and a lot of testing work has been done (and is being done) to ensure that is the case. Even if you’re an LSL pro and rely on things like &amp;lt;code&amp;gt;(some_list != [])&amp;lt;/code&amp;gt; for getting the list length, or strange things like &amp;lt;code&amp;gt;((largestring=&amp;quot;&amp;quot;) + largestring)&amp;lt;/code&amp;gt;, you’re fully covered. Those still work, and now have optimized implementations.&lt;br /&gt;
&lt;br /&gt;
In fact, LSL will get better than ever before, running faster, and using less memory! The generated bytecode for an LSL script in Lua is generally half the size of a Mono script, and most strings take up half as much memory by virtue of using UTF-8 instead of UTF-16.&lt;br /&gt;
&lt;br /&gt;
We’ve taken a lot of care in writing the new LSL compiler to make the scripting experience for people who prefer LSL better than it was before.&lt;br /&gt;
&lt;br /&gt;
=== Are you going to stop adding functions / events to LSL? ===&lt;br /&gt;
&lt;br /&gt;
In the general case, no. A big part of the work we’ve done is making it easy to share functions between LSL and Lua without much extra effort. For events and functions that can be reasonably exposed to LSL (i.e., they only use simple types that LSL has anyway) then LSL should still get those.&lt;br /&gt;
&lt;br /&gt;
For more advanced features, the idea is that there should still be a path for exposing them to LSL. Obviously, with Lua it’s possible to have object-oriented APIs that would not be possible in LSL, but those objects will generally be written in a way that calling a method on a “special” object we introduce in Lua will really boil down to a function call that can be expressed in LSL.&lt;br /&gt;
&lt;br /&gt;
Not a real example, but &amp;lt;code&amp;gt;some_object:giveInventoryList(&amp;quot;folder_name&amp;quot;, inventory_list)&amp;lt;/code&amp;gt; might really boil down to a &amp;lt;code&amp;gt;llGiveInventoryList(some_object_id, &amp;quot;folder_name&amp;quot;, &amp;quot;inventory_list&amp;quot;);&amp;lt;/code&amp;gt; call. In that way, most additional Lua functionality will be a fancy “easier” wrapper around functions shared with LSL!&lt;br /&gt;
&lt;br /&gt;
The caveat is if functions are added that use new types that aren&#039;t in LSL (like functions that take in a &amp;lt;code&amp;gt;key -&amp;gt; value&amp;lt;/code&amp;gt; map) likely won&#039;t be exposed to LSL, as there&#039;s no way to represent them in LSL anyway.&lt;br /&gt;
&lt;br /&gt;
=== Wait, LSL can run on Lua now? How? Why would you do that? ===&lt;br /&gt;
&lt;br /&gt;
A reasonable question! It sounds crazy at first, but as it turns out, LSL maps really well to Lua bytecode, and we already have a compiler that can output both Mono and legacy LSO2 bytecode. Writing a compiler that could output Lua bytecode wasn&#039;t terribly difficult, and it allows LSL scripts to run faster, and be more memory-efficient. Most importantly, it allows us to test behavior of the new scripting engine against the behavior of the existing Mono scripting engine. This helps us make sure we&#039;re doing an apples-to-apples comparison on our benchmarks, and that all the nice things that Mono currently gets us will also work correctly under the new scripting engine.&lt;br /&gt;
&lt;br /&gt;
Additionally, for better or for worse, LSL is here to stay. People have been writing scripts in it for 20 years, and they&#039;re going to be running on the servers for years to come. Performance and memory usage issues with LSL scripts will continue to matter for the foreseeable future. The faster the server can run existing LSL scripts, the more time it can dedicate to running newer Lua scripts. Memory usage has been a particular problem with the existing Mono scripting engine, and this would help us address this.&lt;br /&gt;
&lt;br /&gt;
=== What about running scripts on the viewer-side if I want viewer-side effects? Does this have any interaction with the work adding Lua addons into the viewer? ===&lt;br /&gt;
&lt;br /&gt;
No, the work on adding Lua addons to the viewer is a separate effort. Lua scripts on SL will strictly run on the region, and not on the people&#039;s viewers. We&#039;re focused on providing a high-quality replacement to the existing server-side scripts before we look at anything else.&lt;br /&gt;
&lt;br /&gt;
Note that we are all too aware of the current difficulty of scripting HUD UIs for Second Life (we make a lot of our own) and we&#039;re thinking of ways we can make things better, but there are no definite plans, and it wouldn&#039;t involve running scripts on the viewer in the near-term. The current UI tooling exposed to Lua viewer addons isn&#039;t tooled for being called from untrusted code either way, so lack of &amp;quot;client-side&amp;quot; scripts is unlikely to hinder any effort to create a better API for HUDs for in-world systems.&lt;br /&gt;
&lt;br /&gt;
=== You said Lua runs LSL faster than Mono? It uses less memory? How? I&#039;m suspicious. ===&lt;br /&gt;
&lt;br /&gt;
A reasonable suspicion! We were also suspicious and suspected that Lua would be slightly slower, but in reality, yes, Lua can run LSL scripts quite a bit faster than the custom-patched Mono 2.6 we currently use. The best LSL on Mono has performed compared to Luau in our internal tests is 10% slower than Luau. Similarly, most LSL on Luau scripts end up being around half the size of Mono scripts. That means much more memory to play with in your LSL scripts.&lt;br /&gt;
&lt;br /&gt;
The reason why isn&#039;t intuitive, after all, Mono is a lot more intricate and has a lot of fancy performance optimizations like a JIT optimizer, but it becomes clear when you look at the bytecode that&#039;s generated for LSL on each scripting engine.&lt;br /&gt;
&lt;br /&gt;
Consider the following LSL function as an example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lsl2&amp;quot;&amp;gt;&lt;br /&gt;
float loudSubtract(float val, float sub) {&lt;br /&gt;
    // subtract, telling the owner what we did&lt;br /&gt;
	float new_val = val - sub;&lt;br /&gt;
	llOwnerSay(&amp;quot;New val is &amp;quot; + (string)new_val);&lt;br /&gt;
	return new_val;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This compiles to the following Luau bytecode (subject to further optimization):&lt;br /&gt;
&lt;br /&gt;
{| role=&amp;quot;presentation&amp;quot; class=&amp;quot;wikitable mw-collapsible autocollapse mw-collapsed&amp;quot;&lt;br /&gt;
| &amp;lt;strong&amp;gt;Lua bytecode&amp;lt;/strong&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre&amp;gt;&lt;br /&gt;
Function 0 (_floudSubtract):&lt;br /&gt;
SUB R2 R0 R1&lt;br /&gt;
GETIMPORT R3 2 [ll.OwnerSay]&lt;br /&gt;
GETIMPORT R6 5 [lsl.cast]&lt;br /&gt;
MOVE R7 R2&lt;br /&gt;
LOADN R8 3&lt;br /&gt;
CALL R6 2 1&lt;br /&gt;
LOADK R5 K6 [&#039;New val is &#039;]&lt;br /&gt;
CONCAT R4 R5 R6&lt;br /&gt;
CALL R3 1 0&lt;br /&gt;
RETURN R2 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Okay, that&#039;s pretty decent. Some places where things could be more optimized, but no big deal. The equivalent Mono bytecode is (and bear with us, this is long:)&lt;br /&gt;
&lt;br /&gt;
{| role=&amp;quot;presentation&amp;quot; class=&amp;quot;wikitable mw-collapsible autocollapse mw-collapsed&amp;quot;&lt;br /&gt;
| &amp;lt;strong&amp;gt;Mono bytecode&amp;lt;/strong&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre&amp;gt;&lt;br /&gt;
 .class public auto ansi serializable beforefieldinit&lt;br /&gt;
  LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    extends [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 &#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 val&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; l1&#039;&lt;br /&gt;
&lt;br /&gt;
  .method compilercontrolled specialname rtspecialname instance void&lt;br /&gt;
    .ctor(&lt;br /&gt;
      int32 _param1,&lt;br /&gt;
      class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 _param2,&lt;br /&gt;
      float32 _param3,&lt;br /&gt;
      float32 _param4,&lt;br /&gt;
      float32 _param5,&lt;br /&gt;
      int32 _param6&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 4&lt;br /&gt;
&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: call         instance void [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame::.ctor()&lt;br /&gt;
&lt;br /&gt;
    // [29 5 - 29 28]&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldarg.s      _param1&lt;br /&gt;
    IL_0009: nop&lt;br /&gt;
    IL_000a: nop&lt;br /&gt;
    IL_000b: nop&lt;br /&gt;
    IL_000c: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
    // [30 5 - 30 34]&lt;br /&gt;
    IL_0011: ldarg.0      // this&lt;br /&gt;
    IL_0012: ldarg.s      _param2&lt;br /&gt;
    IL_0014: nop&lt;br /&gt;
    IL_0015: nop&lt;br /&gt;
    IL_0016: nop&lt;br /&gt;
    IL_0017: stfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 23]&lt;br /&gt;
    IL_001c: ldarg.0      // this&lt;br /&gt;
    IL_001d: ldarg.s      _param3&lt;br /&gt;
    IL_001f: nop&lt;br /&gt;
    IL_0020: nop&lt;br /&gt;
    IL_0021: nop&lt;br /&gt;
    IL_0022: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
&lt;br /&gt;
    // [32 5 - 32 23]&lt;br /&gt;
    IL_0027: ldarg.0      // this&lt;br /&gt;
    IL_0028: ldarg.s      _param4&lt;br /&gt;
    IL_002a: nop&lt;br /&gt;
    IL_002b: nop&lt;br /&gt;
    IL_002c: nop&lt;br /&gt;
    IL_002d: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
    // [33 5 - 33 28]&lt;br /&gt;
    IL_0032: ldarg.0      // this&lt;br /&gt;
    IL_0033: ldarg.s      _param5&lt;br /&gt;
    IL_0035: nop&lt;br /&gt;
    IL_0036: nop&lt;br /&gt;
    IL_0037: nop&lt;br /&gt;
    IL_0038: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
    // [34 5 - 34 28]&lt;br /&gt;
    IL_003d: ldarg.0      // this&lt;br /&gt;
    IL_003e: ldarg.s      _param6&lt;br /&gt;
    IL_0040: nop&lt;br /&gt;
    IL_0041: nop&lt;br /&gt;
    IL_0042: nop&lt;br /&gt;
    IL_0043: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0048: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor&lt;br /&gt;
&lt;br /&gt;
  .method public virtual instance object&lt;br /&gt;
    Resume() cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 8&lt;br /&gt;
&lt;br /&gt;
    // [37 37 - 37 99]&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: ldfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
    IL_000c: ldarg.0      // this&lt;br /&gt;
    IL_000d: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
    IL_0012: call         instance float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575::gloudSubtract(float32, float32)&lt;br /&gt;
    IL_0017: box          [mscorlib]System.Single&lt;br /&gt;
    IL_001c: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::Resume&lt;br /&gt;
} // end of class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 // ... Within the actual script class&lt;br /&gt;
&lt;br /&gt;
 .method public hidebysig instance float32&lt;br /&gt;
    gloudSubtract(&lt;br /&gt;
      float32 val,&lt;br /&gt;
      float32 &#039;sub&#039;&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 16&lt;br /&gt;
    .locals init (&lt;br /&gt;
      [0] float32 num1,&lt;br /&gt;
      [1] int32 &#039;l1[34-35], num2[69-151], num2[151-152]&#039;,&lt;br /&gt;
      [2] class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame c9eec9e37575gloudSubtractFrame,&lt;br /&gt;
      [3] float32 num3&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    // [17 5 - 17 60]&lt;br /&gt;
    IL_0000: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsRestoring()&lt;br /&gt;
    IL_0005: brfalse      IL_0036&lt;br /&gt;
&lt;br /&gt;
    // [19 7 - 19 202]&lt;br /&gt;
    IL_000a: call         class [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame [UThread]LindenLab.SecondLife.UThread.UThread::Pop()&lt;br /&gt;
    IL_000f: castclass    LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0014: stloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
    // [20 7 - 20 53]&lt;br /&gt;
    IL_0015: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0016: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
    IL_001b: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [21 7 - 21 55]&lt;br /&gt;
    IL_001c: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_001d: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0022: stloc.1      // l1&lt;br /&gt;
&lt;br /&gt;
    // [22 7 - 22 55]&lt;br /&gt;
    IL_0023: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0024: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
    IL_0029: switch       (IL_0085, IL_004b)&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 58]&lt;br /&gt;
    IL_0036: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_003b: brfalse      IL_004b&lt;br /&gt;
&lt;br /&gt;
    // [33 7 - 33 15]&lt;br /&gt;
    IL_0040: ldc.i4       1 // 0x00000001&lt;br /&gt;
    IL_0045: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [34 7 - 34 20]&lt;br /&gt;
    IL_0046: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [37 5 - 37 70]&lt;br /&gt;
    IL_004b: ldarg.s      &#039;sub&#039;&lt;br /&gt;
    IL_004d: nop&lt;br /&gt;
    IL_004e: nop&lt;br /&gt;
    IL_004f: nop&lt;br /&gt;
    IL_0050: ldarg.s      val&lt;br /&gt;
    IL_0052: nop&lt;br /&gt;
    IL_0053: nop&lt;br /&gt;
    IL_0054: nop&lt;br /&gt;
    IL_0055: call         float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&lt;br /&gt;
    IL_005a: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [38 5 - 38 84]&lt;br /&gt;
    IL_005b: ldloc.0      // num1&lt;br /&gt;
    IL_005c: call         string [LslLibrary]LindenLab.SecondLife.LslRunTime::ToString(float32)&lt;br /&gt;
    IL_0061: ldstr        &amp;quot;New val is &amp;quot;&lt;br /&gt;
    IL_0066: call         string [LslUserScript]LindenLab.SecondLife.LslUserScript::Add(string, string)&lt;br /&gt;
    IL_006b: call         void [LslLibrary]LindenLab.SecondLife.Library::llOwnerSay(string)&lt;br /&gt;
&lt;br /&gt;
    // [39 5 - 39 58]&lt;br /&gt;
    IL_0070: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_0075: brfalse      IL_0085&lt;br /&gt;
&lt;br /&gt;
    // [41 7 - 41 15]&lt;br /&gt;
    IL_007a: ldc.i4       0 // 0x00000000&lt;br /&gt;
    IL_007f: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [42 7 - 42 20]&lt;br /&gt;
    IL_0080: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [45 5 - 45 17]&lt;br /&gt;
    IL_0085: ldloc.0      // num1&lt;br /&gt;
    IL_0086: ret&lt;br /&gt;
    IL_0087: nop&lt;br /&gt;
&lt;br /&gt;
    // [47 5 - 47 194]&lt;br /&gt;
    IL_0088: ldloc.1      // num2&lt;br /&gt;
    IL_0089: ldarg.0      // this&lt;br /&gt;
    IL_008a: ldarg        val&lt;br /&gt;
    IL_008e: nop&lt;br /&gt;
    IL_008f: nop&lt;br /&gt;
    IL_0090: ldarg        &#039;sub&#039;&lt;br /&gt;
    IL_0094: nop&lt;br /&gt;
    IL_0095: nop&lt;br /&gt;
    IL_0096: ldloc.0      // num1&lt;br /&gt;
    IL_0097: ldloc.1      // num2&lt;br /&gt;
    IL_0098: newobj       instance void LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor(int32, class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575, float32, float32, float32, int32)&lt;br /&gt;
    IL_009d: pop&lt;br /&gt;
&lt;br /&gt;
    // [49 5 - 49 17]&lt;br /&gt;
    IL_009e: ldloc.3      // num3&lt;br /&gt;
    IL_009f: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
While this might look unreasonably large, this is also pretty similar to how &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt; C# functions that have &amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt;s get compiled. The code required to allow a function to pause in the middle to let other things run is necessarily very large, and our Mono bytecode instrumentation is essentially a special variant of that which optimizes for cases where the function won&#039;t have to pause. Try pasting the following code into sharplab.io (while decompiling to IL) and you&#039;ll see something very similar:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
using System.Threading.Tasks;&lt;br /&gt;
public class Example {&lt;br /&gt;
    public async Task llOwnerSay(string val)&lt;br /&gt;
    {&lt;br /&gt;
        // stub&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    public async Task&amp;lt;float&amp;gt; loudSubtract(float val, float sub) {&lt;br /&gt;
        float new_val = val - sub;&lt;br /&gt;
        await llOwnerSay(&amp;quot;New val is &amp;quot; + new_val.ToString());&lt;br /&gt;
        return new_val;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Lua bytecode ends up being a lot smaller, because the Lua scripting engine itself knows how to handle this case, and doesn&#039;t need to add any special code to your scripts to allow them to be paused and resumed in the middle of a function.&lt;br /&gt;
&lt;br /&gt;
Even though Mono has a JIT optimizer, which should in theory make the script run faster, this sort of code is very unusual, and the JIT optimizer isn&#039;t able to optimize it very well. That means that even though Luau itself is in theory slower, it&#039;s able to run LSL much faster than Mono can. &lt;br /&gt;
&lt;br /&gt;
You might also notice things like call &amp;lt;code&amp;gt;float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&amp;lt;/code&amp;gt; in the Mono code. Those are necessary because of LSL&#039;s weird operand evaluation order, but are unnecessary in Luau because of the more efficient form of bytecode that Luau uses.&lt;br /&gt;
&lt;br /&gt;
In general, C# doesn&#039;t aim at producing very small bytecode for async functions, which is important for something like Second Life where you might have thousands of different individual scripts running within a region. Luau does generate small bytecode, while still having decent performance, making it a good choice for Second Life. And hey, strings are UTF-8 in Lua instead of UTF-16 as in Mono, so they use much less memory for most data as well.&lt;br /&gt;
&lt;br /&gt;
=== Are we going to get a higher memory limit in scripts? ===&lt;br /&gt;
&lt;br /&gt;
Not at first, Luau uses less memory than Mono, so you shouldn&#039;t really miss it. You&#039;re not going to start hitting script memory limits just because you converted your script to Lua. Using Lua proper should allow you to more precisely control memory usage in your scripts, e.g. by being able to handle memory allocation errors in &amp;lt;code&amp;gt;pcall()&amp;lt;/code&amp;gt; and access to the mutable &amp;lt;code&amp;gt;buffer&amp;lt;/code&amp;gt; class for byte-packing.&lt;br /&gt;
&lt;br /&gt;
There is some interest in giving specific scripts a little more memory if they&#039;re designated &amp;quot;high priority&amp;quot;, like a main &amp;quot;game manager&amp;quot; object for an experience, but there are no specific plans for this at the moment.&lt;br /&gt;
&lt;br /&gt;
=== Are my existing LSL scripts going to run on Lua Automatically? ===&lt;br /&gt;
&lt;br /&gt;
While we can run LSL in Lua now, and the idea is to eventually phase out Mono, there’s no immediate plans to transparently migrate existing scripts over to Lua. There are ideas about how this could be done safely without breaking existing content, but we want to make sure we have everything right before we start work on this.&lt;br /&gt;
&lt;br /&gt;
=== How does interoperation with LSL work? ===&lt;br /&gt;
&lt;br /&gt;
Everything that you expect from LSL should have some equivalent in Lua. There’s &amp;lt;code&amp;gt;key&amp;lt;/code&amp;gt;s, &amp;lt;code&amp;gt;rotation&amp;lt;/code&amp;gt;s and &amp;lt;code&amp;gt;vector&amp;lt;/code&amp;gt;s and something like &amp;lt;code&amp;gt;some_vec * some_rot&amp;lt;/code&amp;gt; in LSL is the exact same in Lua scripts.&lt;br /&gt;
&lt;br /&gt;
All LSL functions will be available in Lua, and something like &amp;lt;code&amp;gt;llSay(0, “hi!”)&amp;lt;/code&amp;gt; is just &amp;lt;code&amp;gt;ll.Say(0, “hi!”)&amp;lt;/code&amp;gt; now. What about &amp;lt;code&amp;gt;llSetPos(llGetPos() + &amp;lt;0, 0, 1&amp;gt;);&amp;lt;/code&amp;gt;? Just &amp;lt;code&amp;gt;ll.SetPos(ll.GetPos() + vector(0, 0, 1))&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
No special magic required here, and you can write whatever fancy wrappers you want around the existing LSL functions. We’ll be looking at how residents actually write these wrappers to inform the design of “official” higher-level Lua APIs, so we’re excited to see your ideas!&lt;br /&gt;
&lt;br /&gt;
=== Can I mix LSL and Lua in the same script? ===&lt;br /&gt;
&lt;br /&gt;
No, although most things should be a direct translation. At some point we may make a tool to help you do this, but there are no specific plans at the moment.&lt;br /&gt;
&lt;br /&gt;
If your object has multiple scripts, you can mix LSL and Lua within the same object, though.&lt;br /&gt;
&lt;br /&gt;
=== Can we use all of Lua? Is anything not there? ===&lt;br /&gt;
&lt;br /&gt;
Most everything will be there, except for two notable exceptions:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;getfenv()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;setfenv()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;_ENV&amp;lt;/code&amp;gt; are not accessible because they necessarily make the script harder to optimize. Usually people only use this for mocking in test code, and this can be accomplished by passing in a parameter with a table of function references rather than pretending you have different global variable values for certain functions.&lt;br /&gt;
* &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; is not supported since it has a huge impact on performance. For those not familiar, this is equivalent to eval() in other languages. Every single time you call &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; the compiler has to go over what you passed in, compile it, and then load the generated bytecode. The Lua compiler isn’t particularly fast, and doing this can cause your script to halt in a way that we can’t interrupt to give other scripts time to run. This is a big part of why &amp;lt;code&amp;gt;eval()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; are generally considered poor programming practice.&lt;br /&gt;
&lt;br /&gt;
The one real use-case we could think of for &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; is a debugger, and we’re looking at implementing a “real” debugger that lets you step through your code. Note that this won’t happen with the initial release, but watch this space!&lt;br /&gt;
&lt;br /&gt;
One thing that is planned, but will likely not be in the initial release is the coroutine library. This is one we’d really like to have in there, so you can do fancy things like have multiple “timers” in a script by just having coroutines that sleep every so often, but multiple running coroutines within the same script doesn’t fit well with the existing scripting architecture the Second Life server uses. Rest assured, this will be in there soon™ after Lua is released, but in the meantime you can still use something like the &amp;lt;code&amp;gt;Promise&amp;lt;/code&amp;gt; chaining that JavaScript uses to write asynchronous event handlers.&lt;br /&gt;
&lt;br /&gt;
We’ll have code demonstrating &amp;quot;proper&amp;quot; usage of Lua around the time that the public beta starts.&lt;br /&gt;
&lt;br /&gt;
=== Why Lua? I wanted C#! ===&lt;br /&gt;
&lt;br /&gt;
Great question!&lt;br /&gt;
&lt;br /&gt;
The server Lua scripting initiative actually started off as an attempt to get C# working on Second Life, basically as a revival of the work started in the 2000s. The initial thought was “this should be pretty easy, most of the work was already done getting everything onto Mono!” A lot of effort was put in over several months to:&lt;br /&gt;
&lt;br /&gt;
* Understand the Mono and .NET Core runtimes&lt;br /&gt;
* Observe how other Engines and Virtual Worlds have embedded C# and scripting in general&lt;br /&gt;
* Understand how recent advancements in scripting technologies (like WebAssembly) might make this integration easier&lt;br /&gt;
* Whether unique properties of how scripting / content creation works on Second Life would make this harder to implement&lt;br /&gt;
&lt;br /&gt;
Unfortunately, the reasons we were ultimately unable to do this were very technical, so there’s no side-stepping a whole lot of tech-speak here, but there were several key properties that made integrating C# in Second Life rather difficult to implement:&lt;br /&gt;
&lt;br /&gt;
==== Sandboxing with reflection is hard ====&lt;br /&gt;
&lt;br /&gt;
Previous proof-of-concept code for C# in Second Life relied on CoreCLR sandboxing in Mono. This approach has fallen out of favor in recent years, because similar approaches led to many, many security holes in Java’s browser plugin. This sandboxing method is not possible in .NET Core, and has been totally removed.&lt;br /&gt;
Since this sandboxing is no longer in .NET, we need to rely on the ability to look at code to determine if it looks “dangerous” before running it. Some people have done this, but it can only be done properly by disabling reflection, which is a core part of C# and .NET! Without reflection, you’re unable to use the fancy third-party Newtonsoft.JSON libraries or whatnot that you would have otherwise been able to use.&lt;br /&gt;
&lt;br /&gt;
Big portions of .NET’s standard library use reflection in a way that would be unsafe to expose to residents for scripting facilities, so you essentially have to maintain a list of functions and classes that are “safe” to use, and write alternatives to functions that are “unsafe” to call, but provide useful functionality. At this point, the scripting language that you’re exposing is a pretty bare-bones C# that needs to be scripted fundamentally differently from how you would write C# in Unity, for example.&lt;br /&gt;
&lt;br /&gt;
Allowing the use of modern C# features would require migration off of Mono and onto .NET core, which would be more involved than integrating Lua. Mono is in maintenance mode at the moment, and only supports the legacy .NET framework&lt;br /&gt;
Ensuring that we’re able to correctly limit each script’s memory usage in a low-impact way is a bit of an unsolved problem in .NET Core. Generally, memory allocation tracking is something that you only do for debugging, but we need to do it for all scripts! What happens if you declare a static global in a C# script? Is the memory usage counted against all scripts of that type? Do all scripts share the data in that static variable?&lt;br /&gt;
&lt;br /&gt;
What about if there’s a function in the standard library that keeps a static cache of data? How do we penalize (or not penalize) scripts for allocations that they didn’t even control? How do we make sure that data remains consistent across script unloads / reloads?&lt;br /&gt;
&lt;br /&gt;
These are problems that most people embedding C# don’t need to solve, and the solutions that do exist are not generally workable into Second Life’s scripting model without multiple months of work. Most other virtual worlds do script sandboxing on a per-parcel or per-region level and put everything into the same logical scripting sandbox, which Second Life does not do. People would be rightly annoyed if someone’s HUD or attachments caused their parcel to go over the memory limit and things stopped working.&lt;br /&gt;
&lt;br /&gt;
==== Making everything micro-threaded with extreme size constraints is hard ====&lt;br /&gt;
&lt;br /&gt;
We need to be able to temporarily pause scripts in the middle of running an event handler so we can ensure we fairly schedule code execution. Previously, we wrote a script to do some quite clever binary instrumentation of .NET assemblies to inject yield points. This works quite well for for LSL scripts, but there are issues with C# in particular.&lt;br /&gt;
&lt;br /&gt;
One is that it doesn’t handle the &amp;lt;code&amp;gt;try&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;finally&amp;lt;/code&amp;gt; construct in C#. While not impossible to handle, this would be a difficult undertaking. Something that would be much more difficult is allowing pausing execution inside a constructor or other such special constructs in C#. While the stack induction and bytecode manipulation done by our current system is very cool, it is quite complex to update, relies on outdated tools that don’t work with new .NET versions, and necessarily requires making the bytecode quite a bit larger than it would otherwise be by injecting hooks.&lt;br /&gt;
&lt;br /&gt;
But that’s not a huge deal, our .NET instrumentation library was written when &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt; was not a thing in C#, so we should be able to inject &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt; into user-provided source code instead to make it possible to transparently pause scripts right? Ehhhh, sort of. There are certain functions that cannot be made &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt; in C# without breaking things. Object constructors are one of those, we have no way to pause and resume inside those. The situation is similar inside &amp;lt;code&amp;gt;ToString()&amp;lt;/code&amp;gt; overloads. If we make a user-provided &amp;lt;code&amp;gt;ToString()&amp;lt;/code&amp;gt; overload async, it’ll just never get called in cases where ToString() would normally get called! And what about implementing &amp;lt;code&amp;gt;IEnumerable.GetIterator()&amp;lt;/code&amp;gt;? That might work… if you did a lot of work of replacing &amp;lt;code&amp;gt;IEnumerable&amp;lt;/code&amp;gt; with &amp;lt;code&amp;gt;IAsyncEnumerable&amp;lt;/code&amp;gt;., and &amp;lt;code&amp;gt;foreach&amp;lt;/code&amp;gt; with &amp;lt;code&amp;gt;await foreach&amp;lt;/code&amp;gt;... sometimes.&lt;br /&gt;
&lt;br /&gt;
Suffice to say, that if you go this route, you end up with something that’s sort-of C# but is missing a lot of things people expect of C#, and may not behave the same as the regular C# that it pretends to be.&lt;br /&gt;
&lt;br /&gt;
==== Saving script state ====&lt;br /&gt;
&lt;br /&gt;
But let’s not forget one of the most important cases where SL is unique: Scripts can move into your inventory, across region boundaries, etc, without having to care too much that it even happened. Even if your script runs forever in a single function, that’s totally fine! For example, this is a valid LSL script:&lt;br /&gt;
&lt;br /&gt;
  default {&lt;br /&gt;
    state_entry() {&lt;br /&gt;
      integer i;&lt;br /&gt;
      while(TRUE) {&lt;br /&gt;
        llSetText(&amp;quot;Run Number &amp;quot; + (string)(i++), &amp;lt;1,1,1&amp;gt;, 1);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
All it does is run forever in the same function, incrementing a number in the floating text above an object. It still works if you take the object into your inventory, it still works if you move the object to another region, as a scripter you don&#039;t really have to care except in limited circumstances.&lt;br /&gt;
&lt;br /&gt;
That&#039;s a pretty contrived example, but there are tons of scripts with a structure like this! Probably a more common example is a math-heavy function that just takes a while to run. Under the current model, you can interrupt what&#039;s going on by just picking up the object, then plop it back down later and let it continue on. There are a number of ways you can handle this without serializing the whole runtime state, like raising an exception in the middle of executing the function, but what are the chances that people will handle this correctly? How many command-line scripts have you used that do something really weird when you pressed &amp;lt;code&amp;gt;CTRL+C&amp;lt;/code&amp;gt;?&lt;br /&gt;
&lt;br /&gt;
To keep things simple for creators, we wanted to keep this nice property of LSL in whatever language we chose. This seemingly simple feature requires a lot of work because, think about it, how many programming languages let you seamlessly halt a program in the middle of it doing something, move it over to another computer, then resume whatever it was doing? Can you do that without doing a full-memory snapshot that would be gigabytes in size? That&#039;s not really a thing that people generally do, but it&#039;s required for us to not have sharp edges on our script creation experience!&lt;br /&gt;
&lt;br /&gt;
We have a version of this in our Mono scripting engine, which was all written in-house. It works very well for LSL, but it has trouble with more complex structures. As it turns out, taking all the state of a running program, serializing it, then transparently deserializing it is a difficult problem, even with data that &amp;lt;i&amp;gt;looks&amp;lt;/i&amp;gt; simple.&lt;br /&gt;
&lt;br /&gt;
Suppose we have something like &amp;lt;code&amp;gt;Dictionary&amp;lt;SomeObject, int&amp;gt;&amp;lt;/code&amp;gt; that we use as a counter for how many times we&#039;ve seen each &amp;lt;code&amp;gt;SomeObject&amp;lt;/code&amp;gt;. What do you do if the &amp;lt;code&amp;gt;GetHashCode()&amp;lt;/code&amp;gt; bucketing changes after the script deserializes and the memory increases? Can you prevent that from happening? Will live &amp;lt;code&amp;gt;IEnumerator&amp;lt;/code&amp;gt;s still work correctly? How can you be sure of this? .NET makes no guarantees about any of this behavior, and good luck digging through the .NET source code to figure out if any of this is even the case.&lt;br /&gt;
&lt;br /&gt;
What about types that aren&#039;t marked &amp;lt;code&amp;gt;[Serializable]&amp;lt;/code&amp;gt;, including types in the standard library? Can people just not use those? Do we have to write special custom serializers for them? Could they just randomly break when the script is deserialized and we accept that as a consequence?&lt;br /&gt;
&lt;br /&gt;
As you can see, there are a lot of hidden sharp edges involved in transparently moving scripts across servers, and the general answer provided when you ask about doing this is &amp;quot;That&#039;s very hard, make users write more intricate scripts that explicitly handle their own serialization and save their own state.&amp;quot; This is fine if most of your users are professional programmers, not so if you have a mix of professional programmers and regular users who just want an object that counts how many people have clicked on their head.&lt;br /&gt;
&lt;br /&gt;
==== Wrapping it up ====&lt;br /&gt;
&lt;br /&gt;
This is a very small subset of the issues we ran into while trying to design a satisfactory C#-based scripting system that fits Second Life&#039;s needs. Seriously, there&#039;s about 20 pages of notes.&lt;br /&gt;
&lt;br /&gt;
Meanwhile, Lua makes most of these things easy. Want to halt a script mid-execution? Okay, set an interrupt handler. Want to serialize your script while it&#039;s running and have it move to another server? Okay, there are libraries that have existed for decades that can do this. Fable&#039;s save file system was based on this. Quite a lot of scripting engine integration problems we have that would rise to the level of extremely difficult and relatively novel Computer Science research in .NET (and by extension, C#) are relatively simple to do with Lua.&lt;br /&gt;
&lt;br /&gt;
=== Okay, but seriously, why Lua specifically? Because it&#039;s popular for games? Because &amp;lt;other platform&amp;gt; uses it? Why not &amp;lt;X&amp;gt;? ===&lt;br /&gt;
&lt;br /&gt;
Actually, no. Neither other users of Lua, or a specific love of the language had anything to do with the decision. If you prefer a different programming language, there&#039;s a good chance we considered it. We actually looked at quite a lot of different languages and their associated engines, including JavaScript and WebAssembly, Lua was considered fairly late in the process. The ones below are just a small subset of the ones that we looked at:&lt;br /&gt;
&lt;br /&gt;
[[File:Lsl_vm_comparison.png|1000px]]&lt;br /&gt;
&lt;br /&gt;
As you can see, Luau was the only scripting runtime that fulfilled all of our requirements for a scripting engine within Second Life, and we arrived at it quite late in the decision process. We wanted something that we knew could deliver a high-quality scripting experience to creators, and we believe that Lua can best provide it.&lt;br /&gt;
&lt;br /&gt;
=== I genuinely hate Lua and refuse to use it ===&lt;br /&gt;
&lt;br /&gt;
A lot of people feel the same about LSL! Kidding aside, this is a totally defensible position, Lua can be a quirky language. Yes, it&#039;s strange that array indices start at 1. Yes, sometimes you can forget that you join strings with &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt;. It can take some getting used to.&lt;br /&gt;
&lt;br /&gt;
A big part of the reason why we&#039;re using Luau is due to the quality of the scripting engine itself, not specifically the language. A lot of issues people had with older versions of Lua have been addressed in Luau, and ones that haven&#039;t been for one reason or another can be easily dealt with since we have such a high degree of control over the scripting engine now. Luau is very easy to modify. If we find that there are real quality-of-life issues with Lua that people run into while writing scripts, we can just change how it works, assuming the benefits outweigh the cost of breaking interoperability with Lua scripts from elsewhere.&lt;br /&gt;
&lt;br /&gt;
But hey, maybe you really still hate the idea of using Lua. That&#039;s fine! The nice thing about having Lua instead of just LSL is there are a number of languages that can compile down to Lua code that could potentially work in Second Life! People have done work to get TypeScript and C# compiling to Lua, and it works pretty well for them, and there&#039;s also more exotic languages like MoonScript that target Lua specifically. See this GitHub repo for a listing: https://github.com/hengestone/lua-languages&lt;br /&gt;
&lt;br /&gt;
If you&#039;re so inclined, you can go as far as to take the Luau transpiler and hack it up to support &amp;lt;code&amp;gt;!=&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;//&amp;lt;/code&amp;gt; comments and really whatever you want. So long as it can take that source and output it to standard Lua, you&#039;re good.&lt;br /&gt;
&lt;br /&gt;
=== Are you going to be using Lua 5.1? Why not 5.4? ===&lt;br /&gt;
&lt;br /&gt;
We&#039;re using Luau as the base, which is &#039;&#039;compatible&#039;&#039; with Lua 5.1 but not necessarily Lua 5.1. It includes various features of PUC-Rio&#039;s Lua implementation up to and including 5.4. The Luau Compatibility reference is a good guide for which features are taken from which versions of PUC-Rio Lua: https://luau-lang.org/compatibility&lt;br /&gt;
&lt;br /&gt;
=== Are there going to be integer types in SL&#039;s Lua implementation? ===&lt;br /&gt;
&lt;br /&gt;
We have added 32-bit integers but they are strictly for compatibility with existing LSL APIs where integer type identity is important within a list (&amp;lt;code&amp;gt;llSetPrimitiveParams()&amp;lt;/code&amp;gt; and friends.) They don&#039;t have a literal form in Luau scripts and in general you should just use regular Lua numbers if you care about performance, since the Luau VM optimizes for their use, but technically you can use them. The Luau project discussions have some explanation as to why integers aren&#039;t a first-class user-facing type in Luau https://github.com/luau-lang/luau/discussions/242#discussioncomment-1707387.&lt;br /&gt;
&lt;br /&gt;
==== Are integer operations supported at all, then? (i.e. bitwise operators) ====&lt;br /&gt;
&lt;br /&gt;
Yes, through the &amp;lt;code&amp;gt;bit32&amp;lt;/code&amp;gt; library. In fact, &#039;&#039;more&#039;&#039; integer operations than are currently supported in LSL, like logical shift right and rotate operations. See https://luau-lang.org/library#bit32-library .&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1216908</id>
		<title>Lua FAQ</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1216908"/>
		<updated>2024-07-09T22:56:16Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
=== Can I keep using LSL? ===&lt;br /&gt;
&lt;br /&gt;
Yep! LSL should work exactly the same as before, and a lot of testing work has been done (and is being done) to ensure that is the case. Even if you’re an LSL pro and rely on things like &amp;lt;code&amp;gt;(some_list != [])&amp;lt;/code&amp;gt; for getting the list length, or strange things like &amp;lt;code&amp;gt;((largestring=&amp;quot;&amp;quot;) + largestring)&amp;lt;/code&amp;gt;, you’re fully covered. Those still work, and now have optimized implementations.&lt;br /&gt;
&lt;br /&gt;
In fact, LSL will get better than ever before, running faster, and using less memory! The generated bytecode for an LSL script in Lua is generally half the size of a Mono script, and most strings take up half as much memory by virtue of using UTF-8 instead of UTF-16.&lt;br /&gt;
&lt;br /&gt;
We’ve taken a lot of care in writing the new LSL compiler to make the scripting experience for people who prefer LSL better than it was before.&lt;br /&gt;
&lt;br /&gt;
=== Are you going to stop adding functions / events to LSL? ===&lt;br /&gt;
&lt;br /&gt;
In the general case, no. A big part of the work we’ve done is making it easy to share functions between LSL and Lua without much extra effort. For events and functions that can be reasonably exposed to LSL (i.e., they only use simple types that LSL has anyway) then LSL should still get those.&lt;br /&gt;
&lt;br /&gt;
For more advanced features, the idea is that there should still be a path for exposing them to LSL. Obviously, with Lua it’s possible to have object-oriented APIs that would not be possible in LSL, but those objects will generally be written in a way that calling a method on a “special” object we introduce in Lua will really boil down to a function call that can be expressed in LSL.&lt;br /&gt;
&lt;br /&gt;
Not a real example, but &amp;lt;code&amp;gt;some_object:giveInventoryList(&amp;quot;folder_name&amp;quot;, inventory_list)&amp;lt;/code&amp;gt; might really boil down to a &amp;lt;code&amp;gt;llGiveInventoryList(some_object_id, &amp;quot;folder_name&amp;quot;, &amp;quot;inventory_list&amp;quot;);&amp;lt;/code&amp;gt; call. In that way, most additional Lua functionality will be a fancy “easier” wrapper around functions shared with LSL!&lt;br /&gt;
&lt;br /&gt;
The caveat is if functions are added that use new types that aren&#039;t in LSL (like functions that take in a &amp;lt;code&amp;gt;key -&amp;gt; value&amp;lt;/code&amp;gt; map) likely won&#039;t be exposed to LSL, as there&#039;s no way to represent them in LSL anyway.&lt;br /&gt;
&lt;br /&gt;
=== Wait, LSL can run on Lua now? How? Why would you do that? ===&lt;br /&gt;
&lt;br /&gt;
A reasonable question! It sounds crazy at first, but as it turns out, LSL maps really well to Lua bytecode, and we already have a compiler that can output both Mono and legacy LSO2 bytecode. Writing a compiler that could output Lua bytecode wasn&#039;t terribly difficult, and it allows LSL scripts to run faster, and be more memory-efficient. Most importantly, it allows us to test behavior of the new scripting engine against the behavior of the existing Mono scripting engine. This helps us make sure we&#039;re doing an apples-to-apples comparison on our benchmarks, and that all the nice things that Mono currently gets us will also work correctly under the new scripting engine.&lt;br /&gt;
&lt;br /&gt;
Additionally, for better or for worse, LSL is here to stay. People have been writing scripts in it for 20 years, and they&#039;re going to be running on the servers for years to come. Performance and memory usage issues with LSL scripts will continue to matter for the foreseeable future. The faster the server can run existing LSL scripts, the more time it can dedicate to running newer Lua scripts. Memory usage has been a particular problem with the existing Mono scripting engine, and this would help us address this.&lt;br /&gt;
&lt;br /&gt;
=== What about running scripts on the viewer-side if I want viewer-side effects? Does this have any interaction with the work adding Lua addons into the viewer? ===&lt;br /&gt;
&lt;br /&gt;
No, the work on adding Lua addons to the viewer is a separate effort. Lua scripts on SL will strictly run on the region, and not on the people&#039;s viewers. We&#039;re focused on providing a high-quality replacement to the existing server-side scripts before we look at anything else.&lt;br /&gt;
&lt;br /&gt;
Note that we are all too aware of the current difficulty of scripting HUD UIs for Second Life (we make a lot of our own) and we&#039;re thinking of ways we can make things better, but there are no definite plans, and it wouldn&#039;t involve running scripts on the viewer in the near-term. The current UI tooling exposed to Lua viewer addons isn&#039;t tooled for being called from untrusted code either way, so lack of &amp;quot;client-side&amp;quot; scripts is unlikely to hinder any effort to create a better API for HUDs for in-world systems.&lt;br /&gt;
&lt;br /&gt;
=== You said Lua runs LSL faster than Mono? It uses less memory? How? I&#039;m suspicious. ===&lt;br /&gt;
&lt;br /&gt;
A reasonable suspicion! We were also suspicious and suspected that Lua would be slightly slower, but in reality, yes, Lua can run LSL scripts quite a bit faster than the custom-patched Mono 2.6 we currently use. The best LSL on Mono has performed compared to Luau in our internal tests is 10% slower than Luau. Similarly, most LSL on Luau scripts end up being around half the size of Mono scripts. That means much more memory to play with in your LSL scripts.&lt;br /&gt;
&lt;br /&gt;
The reason why isn&#039;t intuitive, after all, Mono is a lot more intricate and has a lot of fancy performance optimizations like a JIT optimizer, but it becomes clear when you look at the bytecode that&#039;s generated for LSL on each scripting engine.&lt;br /&gt;
&lt;br /&gt;
Consider the following LSL function as an example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lsl2&amp;quot;&amp;gt;&lt;br /&gt;
float loudSubtract(float val, float sub) {&lt;br /&gt;
    // subtract, telling the owner what we did&lt;br /&gt;
	float new_val = val - sub;&lt;br /&gt;
	llOwnerSay(&amp;quot;New val is &amp;quot; + (string)new_val);&lt;br /&gt;
	return new_val;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This compiles to the following Luau bytecode (subject to further optimization):&lt;br /&gt;
&lt;br /&gt;
{| role=&amp;quot;presentation&amp;quot; class=&amp;quot;wikitable mw-collapsible autocollapse mw-collapsed&amp;quot;&lt;br /&gt;
| &amp;lt;strong&amp;gt;Lua bytecode&amp;lt;/strong&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre&amp;gt;&lt;br /&gt;
Function 0 (_floudSubtract):&lt;br /&gt;
SUB R2 R0 R1&lt;br /&gt;
GETIMPORT R3 2 [ll.OwnerSay]&lt;br /&gt;
GETIMPORT R6 5 [lsl.cast]&lt;br /&gt;
MOVE R7 R2&lt;br /&gt;
LOADN R8 3&lt;br /&gt;
CALL R6 2 1&lt;br /&gt;
LOADK R5 K6 [&#039;New val is &#039;]&lt;br /&gt;
CONCAT R4 R5 R6&lt;br /&gt;
CALL R3 1 0&lt;br /&gt;
RETURN R2 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Okay, that&#039;s pretty decent. Some places where things could be more optimized, but no big deal. The equivalent Mono bytecode is (and bear with us, this is long:)&lt;br /&gt;
&lt;br /&gt;
{| role=&amp;quot;presentation&amp;quot; class=&amp;quot;wikitable mw-collapsible autocollapse mw-collapsed&amp;quot;&lt;br /&gt;
| &amp;lt;strong&amp;gt;Mono bytecode&amp;lt;/strong&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre&amp;gt;&lt;br /&gt;
 .class public auto ansi serializable beforefieldinit&lt;br /&gt;
  LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    extends [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 &#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 val&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; l1&#039;&lt;br /&gt;
&lt;br /&gt;
  .method compilercontrolled specialname rtspecialname instance void&lt;br /&gt;
    .ctor(&lt;br /&gt;
      int32 _param1,&lt;br /&gt;
      class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 _param2,&lt;br /&gt;
      float32 _param3,&lt;br /&gt;
      float32 _param4,&lt;br /&gt;
      float32 _param5,&lt;br /&gt;
      int32 _param6&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 4&lt;br /&gt;
&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: call         instance void [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame::.ctor()&lt;br /&gt;
&lt;br /&gt;
    // [29 5 - 29 28]&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldarg.s      _param1&lt;br /&gt;
    IL_0009: nop&lt;br /&gt;
    IL_000a: nop&lt;br /&gt;
    IL_000b: nop&lt;br /&gt;
    IL_000c: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
    // [30 5 - 30 34]&lt;br /&gt;
    IL_0011: ldarg.0      // this&lt;br /&gt;
    IL_0012: ldarg.s      _param2&lt;br /&gt;
    IL_0014: nop&lt;br /&gt;
    IL_0015: nop&lt;br /&gt;
    IL_0016: nop&lt;br /&gt;
    IL_0017: stfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 23]&lt;br /&gt;
    IL_001c: ldarg.0      // this&lt;br /&gt;
    IL_001d: ldarg.s      _param3&lt;br /&gt;
    IL_001f: nop&lt;br /&gt;
    IL_0020: nop&lt;br /&gt;
    IL_0021: nop&lt;br /&gt;
    IL_0022: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
&lt;br /&gt;
    // [32 5 - 32 23]&lt;br /&gt;
    IL_0027: ldarg.0      // this&lt;br /&gt;
    IL_0028: ldarg.s      _param4&lt;br /&gt;
    IL_002a: nop&lt;br /&gt;
    IL_002b: nop&lt;br /&gt;
    IL_002c: nop&lt;br /&gt;
    IL_002d: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
    // [33 5 - 33 28]&lt;br /&gt;
    IL_0032: ldarg.0      // this&lt;br /&gt;
    IL_0033: ldarg.s      _param5&lt;br /&gt;
    IL_0035: nop&lt;br /&gt;
    IL_0036: nop&lt;br /&gt;
    IL_0037: nop&lt;br /&gt;
    IL_0038: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
    // [34 5 - 34 28]&lt;br /&gt;
    IL_003d: ldarg.0      // this&lt;br /&gt;
    IL_003e: ldarg.s      _param6&lt;br /&gt;
    IL_0040: nop&lt;br /&gt;
    IL_0041: nop&lt;br /&gt;
    IL_0042: nop&lt;br /&gt;
    IL_0043: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0048: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor&lt;br /&gt;
&lt;br /&gt;
  .method public virtual instance object&lt;br /&gt;
    Resume() cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 8&lt;br /&gt;
&lt;br /&gt;
    // [37 37 - 37 99]&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: ldfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
    IL_000c: ldarg.0      // this&lt;br /&gt;
    IL_000d: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
    IL_0012: call         instance float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575::gloudSubtract(float32, float32)&lt;br /&gt;
    IL_0017: box          [mscorlib]System.Single&lt;br /&gt;
    IL_001c: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::Resume&lt;br /&gt;
} // end of class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 // ... Within the actual script class&lt;br /&gt;
&lt;br /&gt;
 .method public hidebysig instance float32&lt;br /&gt;
    gloudSubtract(&lt;br /&gt;
      float32 val,&lt;br /&gt;
      float32 &#039;sub&#039;&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 16&lt;br /&gt;
    .locals init (&lt;br /&gt;
      [0] float32 num1,&lt;br /&gt;
      [1] int32 &#039;l1[34-35], num2[69-151], num2[151-152]&#039;,&lt;br /&gt;
      [2] class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame c9eec9e37575gloudSubtractFrame,&lt;br /&gt;
      [3] float32 num3&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    // [17 5 - 17 60]&lt;br /&gt;
    IL_0000: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsRestoring()&lt;br /&gt;
    IL_0005: brfalse      IL_0036&lt;br /&gt;
&lt;br /&gt;
    // [19 7 - 19 202]&lt;br /&gt;
    IL_000a: call         class [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame [UThread]LindenLab.SecondLife.UThread.UThread::Pop()&lt;br /&gt;
    IL_000f: castclass    LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0014: stloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
    // [20 7 - 20 53]&lt;br /&gt;
    IL_0015: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0016: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
    IL_001b: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [21 7 - 21 55]&lt;br /&gt;
    IL_001c: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_001d: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0022: stloc.1      // l1&lt;br /&gt;
&lt;br /&gt;
    // [22 7 - 22 55]&lt;br /&gt;
    IL_0023: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0024: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
    IL_0029: switch       (IL_0085, IL_004b)&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 58]&lt;br /&gt;
    IL_0036: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_003b: brfalse      IL_004b&lt;br /&gt;
&lt;br /&gt;
    // [33 7 - 33 15]&lt;br /&gt;
    IL_0040: ldc.i4       1 // 0x00000001&lt;br /&gt;
    IL_0045: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [34 7 - 34 20]&lt;br /&gt;
    IL_0046: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [37 5 - 37 70]&lt;br /&gt;
    IL_004b: ldarg.s      &#039;sub&#039;&lt;br /&gt;
    IL_004d: nop&lt;br /&gt;
    IL_004e: nop&lt;br /&gt;
    IL_004f: nop&lt;br /&gt;
    IL_0050: ldarg.s      val&lt;br /&gt;
    IL_0052: nop&lt;br /&gt;
    IL_0053: nop&lt;br /&gt;
    IL_0054: nop&lt;br /&gt;
    IL_0055: call         float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&lt;br /&gt;
    IL_005a: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [38 5 - 38 84]&lt;br /&gt;
    IL_005b: ldloc.0      // num1&lt;br /&gt;
    IL_005c: call         string [LslLibrary]LindenLab.SecondLife.LslRunTime::ToString(float32)&lt;br /&gt;
    IL_0061: ldstr        &amp;quot;New val is &amp;quot;&lt;br /&gt;
    IL_0066: call         string [LslUserScript]LindenLab.SecondLife.LslUserScript::Add(string, string)&lt;br /&gt;
    IL_006b: call         void [LslLibrary]LindenLab.SecondLife.Library::llOwnerSay(string)&lt;br /&gt;
&lt;br /&gt;
    // [39 5 - 39 58]&lt;br /&gt;
    IL_0070: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_0075: brfalse      IL_0085&lt;br /&gt;
&lt;br /&gt;
    // [41 7 - 41 15]&lt;br /&gt;
    IL_007a: ldc.i4       0 // 0x00000000&lt;br /&gt;
    IL_007f: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [42 7 - 42 20]&lt;br /&gt;
    IL_0080: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [45 5 - 45 17]&lt;br /&gt;
    IL_0085: ldloc.0      // num1&lt;br /&gt;
    IL_0086: ret&lt;br /&gt;
    IL_0087: nop&lt;br /&gt;
&lt;br /&gt;
    // [47 5 - 47 194]&lt;br /&gt;
    IL_0088: ldloc.1      // num2&lt;br /&gt;
    IL_0089: ldarg.0      // this&lt;br /&gt;
    IL_008a: ldarg        val&lt;br /&gt;
    IL_008e: nop&lt;br /&gt;
    IL_008f: nop&lt;br /&gt;
    IL_0090: ldarg        &#039;sub&#039;&lt;br /&gt;
    IL_0094: nop&lt;br /&gt;
    IL_0095: nop&lt;br /&gt;
    IL_0096: ldloc.0      // num1&lt;br /&gt;
    IL_0097: ldloc.1      // num2&lt;br /&gt;
    IL_0098: newobj       instance void LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor(int32, class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575, float32, float32, float32, int32)&lt;br /&gt;
    IL_009d: pop&lt;br /&gt;
&lt;br /&gt;
    // [49 5 - 49 17]&lt;br /&gt;
    IL_009e: ldloc.3      // num3&lt;br /&gt;
    IL_009f: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
While this might look unreasonably large, this is also pretty similar to how &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt; C# functions that have &amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt;s get compiled. The code required to allow a function to pause in the middle to let other things run is necessarily very large, and our Mono bytecode instrumentation is essentially a special variant of that which optimizes for cases where the function won&#039;t have to pause. Try pasting the following code into sharplab.io (while decompiling to IL) and you&#039;ll see something very similar:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
using System.Threading.Tasks;&lt;br /&gt;
public class Example {&lt;br /&gt;
    public async Task llOwnerSay(string val)&lt;br /&gt;
    {&lt;br /&gt;
        // stub&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    public async Task&amp;lt;float&amp;gt; loudSubtract(float val, float sub) {&lt;br /&gt;
        float new_val = val - sub;&lt;br /&gt;
        await llOwnerSay(&amp;quot;New val is &amp;quot; + new_val.ToString());&lt;br /&gt;
        return new_val;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Lua bytecode ends up being a lot smaller, because the Lua scripting engine itself knows how to handle this case, and doesn&#039;t need to add any special code to your scripts to allow them to be paused and resumed in the middle of a function.&lt;br /&gt;
&lt;br /&gt;
Even though Mono has a JIT optimizer, which should in theory make the script run faster, this sort of code is very unusual, and the JIT optimizer isn&#039;t able to optimize it very well. That means that even though Luau itself is in theory slower, it&#039;s able to run LSL much faster than Mono can. &lt;br /&gt;
&lt;br /&gt;
You might also notice things like call &amp;lt;code&amp;gt;float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&amp;lt;/code&amp;gt; in the Mono code. Those are necessary because of LSL&#039;s weird operand evaluation order, but are unnecessary in Luau because of the more efficient form of bytecode that Luau uses.&lt;br /&gt;
&lt;br /&gt;
In general, C# doesn&#039;t aim at producing very small bytecode for async functions, which is important for something like Second Life where you might have thousands of different individual scripts running within a region. Luau does generate small bytecode, while still having decent performance, making it a good choice for Second Life. And hey, strings are UTF-8 in Lua instead of UTF-16 as in Mono, so they use much less memory for most data as well.&lt;br /&gt;
&lt;br /&gt;
=== Are we going to get a higher memory limit in scripts? ===&lt;br /&gt;
&lt;br /&gt;
Not at first, Luau uses less memory than Mono, so you shouldn&#039;t really miss it. You&#039;re not going to start hitting script memory limits just because you converted your script to Lua. Using Lua proper should allow you to more precisely control memory usage in your scripts, e.g. by being able to handle memory allocation errors in &amp;lt;code&amp;gt;pcall()&amp;lt;/code&amp;gt; and access to the mutable &amp;lt;code&amp;gt;buffer&amp;lt;/code&amp;gt; class for byte-packing.&lt;br /&gt;
&lt;br /&gt;
There is some interest in giving specific scripts a little more memory if they&#039;re designated &amp;quot;high priority&amp;quot;, like a main &amp;quot;game manager&amp;quot; object for an experience, but there are no specific plans for this at the moment.&lt;br /&gt;
&lt;br /&gt;
=== Are my existing LSL scripts going to run on Lua Automatically? ===&lt;br /&gt;
&lt;br /&gt;
While we can run LSL in Lua now, and the idea is to eventually phase out Mono, there’s no immediate plans to transparently migrate existing scripts over to Lua. There are ideas about how this could be done safely without breaking existing content, but we want to make sure we have everything right before we start work on this.&lt;br /&gt;
&lt;br /&gt;
=== How does interoperation with LSL work? ===&lt;br /&gt;
&lt;br /&gt;
Everything that you expect from LSL should have some equivalent in Lua. There’s &amp;lt;code&amp;gt;key&amp;lt;/code&amp;gt;s, &amp;lt;code&amp;gt;rotation&amp;lt;/code&amp;gt;s and &amp;lt;code&amp;gt;vector&amp;lt;/code&amp;gt;s and something like &amp;lt;code&amp;gt;some_vec * some_rot&amp;lt;/code&amp;gt; in LSL is the exact same in Lua scripts.&lt;br /&gt;
&lt;br /&gt;
All LSL functions will be available in Lua, and something like &amp;lt;code&amp;gt;llSay(0, “hi!”)&amp;lt;/code&amp;gt; is just &amp;lt;code&amp;gt;ll.Say(0, “hi!”)&amp;lt;/code&amp;gt; now. What about &amp;lt;code&amp;gt;llSetPos(llGetPos() + &amp;lt;0, 0, 1&amp;gt;);&amp;lt;/code&amp;gt;? Just &amp;lt;code&amp;gt;ll.SetPos(ll.GetPos() + vector(0, 0, 1))&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
No special magic required here, and you can write whatever fancy wrappers you want around the existing LSL functions. We’ll be looking at how residents actually write these wrappers to inform the design of “official” higher-level Lua APIs, so we’re excited to see your ideas!&lt;br /&gt;
&lt;br /&gt;
=== Can I mix LSL and Lua in the same script? ===&lt;br /&gt;
&lt;br /&gt;
No, although most things should be a direct translation. At some point we may make a tool to help you do this, but there are no specific plans at the moment.&lt;br /&gt;
&lt;br /&gt;
If your object has multiple scripts, you can mix LSL and Lua within the same object, though.&lt;br /&gt;
&lt;br /&gt;
=== Can we use all of Lua? Is anything not there? ===&lt;br /&gt;
&lt;br /&gt;
Most everything will be there, except for two notable exceptions:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;getfenv()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;setfenv()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;_ENV&amp;lt;/code&amp;gt; are not accessible because they necessarily make the script harder to optimize. Usually people only use this for mocking in test code, and this can be accomplished by passing in a parameter with a table of function references rather than pretending you have different global variable values for certain functions.&lt;br /&gt;
* &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; is not supported since it has a huge impact on performance. For those not familiar, this is equivalent to eval() in other languages. Every single time you call &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; the compiler has to go over what you passed in, compile it, and then load the generated bytecode. The Lua compiler isn’t particularly fast, and doing this can cause your script to halt in a way that we can’t interrupt to give other scripts time to run. This is a big part of why &amp;lt;code&amp;gt;eval()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; are generally considered poor programming practice.&lt;br /&gt;
&lt;br /&gt;
The one real use-case we could think of for &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; is a debugger, and we’re looking at implementing a “real” debugger that lets you step through your code. Note that this won’t happen with the initial release, but watch this space!&lt;br /&gt;
&lt;br /&gt;
One thing that is planned, but will likely not be in the initial release is the coroutine library. This is one we’d really like to have in there, so you can do fancy things like have multiple “timers” in a script by just having coroutines that sleep every so often, but multiple running coroutines within the same script doesn’t fit well with the existing scripting architecture the Second Life server uses. Rest assured, this will be in there soon™ after Lua is released, but in the meantime you can still use something like the &amp;lt;code&amp;gt;Promise&amp;lt;/code&amp;gt; chaining that JavaScript uses to write asynchronous event handlers.&lt;br /&gt;
&lt;br /&gt;
We’ll have code demonstrating &amp;quot;proper&amp;quot; usage of Lua around the time that the public beta starts.&lt;br /&gt;
&lt;br /&gt;
=== Why Lua? I wanted C#! ===&lt;br /&gt;
&lt;br /&gt;
Great question!&lt;br /&gt;
&lt;br /&gt;
The server Lua scripting initiative actually started off as an attempt to get C# working on Second Life, basically as a revival of the work started in the 2000s. The initial thought was “this should be pretty easy, most of the work was already done getting everything onto Mono!” A lot of effort was put in over several months to:&lt;br /&gt;
&lt;br /&gt;
* Understand the Mono and .NET Core runtimes&lt;br /&gt;
* Observe how other Engines and Virtual Worlds have embedded C# and scripting in general&lt;br /&gt;
* Understand how recent advancements in scripting technologies (like WebAssembly) might make this integration easier&lt;br /&gt;
* Whether unique properties of how scripting / content creation works on Second Life would make this harder to implement&lt;br /&gt;
&lt;br /&gt;
Unfortunately, the reasons we were ultimately unable to do this were very technical, so there’s no side-stepping a whole lot of tech-speak here, but there were several key properties that made integrating C# in Second Life rather difficult to implement:&lt;br /&gt;
&lt;br /&gt;
==== Sandboxing with reflection is hard ====&lt;br /&gt;
&lt;br /&gt;
Previous proof-of-concept code for C# in Second Life relied on CoreCLR sandboxing in Mono. This approach has fallen out of favor in recent years, because similar approaches led to many, many security holes in Java’s browser plugin. This sandboxing method is not possible in .NET Core, and has been totally removed.&lt;br /&gt;
Since this sandboxing is no longer in .NET, we need to rely on the ability to look at code to determine if it looks “dangerous” before running it. Some people have done this, but it can only be done properly by disabling reflection, which is a core part of C# and .NET! Without reflection, you’re unable to use the fancy third-party Newtonsoft.JSON libraries or whatnot that you would have otherwise been able to use.&lt;br /&gt;
&lt;br /&gt;
Big portions of .NET’s standard library use reflection in a way that would be unsafe to expose to residents for scripting facilities, so you essentially have to maintain a list of functions and classes that are “safe” to use, and write alternatives to functions that are “unsafe” to call, but provide useful functionality. At this point, the scripting language that you’re exposing is a pretty bare-bones C# that needs to be scripted fundamentally differently from how you would write C# in Unity, for example.&lt;br /&gt;
&lt;br /&gt;
Allowing the use of modern C# features would require migration off of Mono and onto .NET core, which would be more involved than integrating Lua. Mono is in maintenance mode at the moment, and only supports the legacy .NET framework&lt;br /&gt;
Ensuring that we’re able to correctly limit each script’s memory usage in a low-impact way is a bit of an unsolved problem in .NET Core. Generally, memory allocation tracking is something that you only do for debugging, but we need to do it for all scripts! What happens if you declare a static global in a C# script? Is the memory usage counted against all scripts of that type? Do all scripts share the data in that static variable?&lt;br /&gt;
&lt;br /&gt;
What about if there’s a function in the standard library that keeps a static cache of data? How do we penalize (or not penalize) scripts for allocations that they didn’t even control? How do we make sure that data remains consistent across script unloads / reloads?&lt;br /&gt;
&lt;br /&gt;
These are problems that most people embedding C# don’t need to solve, and the solutions that do exist are not generally workable into Second Life’s scripting model without multiple months of work. Most other virtual worlds do script sandboxing on a per-parcel or per-region level and put everything into the same logical scripting sandbox, which Second Life does not do. People would be rightly annoyed if someone’s HUD or attachments caused their parcel to go over the memory limit and things stopped working.&lt;br /&gt;
&lt;br /&gt;
==== Making everything micro-threaded with extreme size constraints is hard ====&lt;br /&gt;
&lt;br /&gt;
We need to be able to temporarily pause scripts in the middle of running an event handler so we can ensure we fairly schedule code execution. Previously, we wrote a script to do some quite clever binary instrumentation of .NET assemblies to inject yield points. This works quite well for for LSL scripts, but there are issues with C# in particular.&lt;br /&gt;
&lt;br /&gt;
One is that it doesn’t handle the &amp;lt;code&amp;gt;try&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;finally&amp;lt;/code&amp;gt; construct in C#. While not impossible to handle, this would be a difficult undertaking. Something that would be much more difficult is allowing pausing execution inside a constructor or other such special constructs in C#. While the stack induction and bytecode manipulation done by our current system is very cool, it is quite complex to update, relies on outdated tools that don’t work with new .NET versions, and necessarily requires making the bytecode quite a bit larger than it would otherwise be by injecting hooks.&lt;br /&gt;
&lt;br /&gt;
But that’s not a huge deal, our .NET instrumentation library was written when &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt; was not a thing in C#, so we should be able to inject &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt; into user-provided source code instead to make it possible to transparently pause scripts right? Ehhhh, sort of. There are certain functions that cannot be made &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt; in C# without breaking things. Object constructors are one of those, we have no way to pause and resume inside those. The situation is similar inside &amp;lt;code&amp;gt;ToString()&amp;lt;/code&amp;gt; overloads. If we make a user-provided &amp;lt;code&amp;gt;ToString()&amp;lt;/code&amp;gt; overload async, it’ll just never get called in cases where ToString() would normally get called! And what about implementing &amp;lt;code&amp;gt;IEnumerable.GetIterator()&amp;lt;/code&amp;gt;? That might work… if you did a lot of work of replacing &amp;lt;code&amp;gt;IEnumerable&amp;lt;/code&amp;gt; with &amp;lt;code&amp;gt;IAsyncEnumerable&amp;lt;/code&amp;gt;., and &amp;lt;code&amp;gt;foreach&amp;lt;/code&amp;gt; with &amp;lt;code&amp;gt;await foreach&amp;lt;/code&amp;gt;... sometimes.&lt;br /&gt;
&lt;br /&gt;
Suffice to say, that if you go this route, you end up with something that’s sort-of C# but is missing a lot of things people expect of C#, and may not behave the same as the regular C# that it pretends to be.&lt;br /&gt;
&lt;br /&gt;
==== Saving script state ====&lt;br /&gt;
&lt;br /&gt;
But let’s not forget one of the most important cases where SL is unique: Scripts can move into your inventory, across region boundaries, etc, without having to care too much that it even happened. Even if your script runs forever in a single function, that’s totally fine! For example, this is a valid LSL script:&lt;br /&gt;
&lt;br /&gt;
  default {&lt;br /&gt;
    state_entry() {&lt;br /&gt;
      integer i;&lt;br /&gt;
      while(TRUE) {&lt;br /&gt;
        llSetText(&amp;quot;Run Number &amp;quot; + (string)(i++), &amp;lt;1,1,1&amp;gt;, 1);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
All it does is run forever in the same function, incrementing a number in the floating text above an object. It still works if you take the object into your inventory, it still works if you move the object to another region, as a scripter you don&#039;t really have to care except in limited circumstances.&lt;br /&gt;
&lt;br /&gt;
That&#039;s a pretty contrived example, but there are tons of scripts with a structure like this! Probably a more common example is a math-heavy function that just takes a while to run. Under the current model, you can interrupt what&#039;s going on by just picking up the object, then plop it back down later and let it continue on. There are a number of ways you can handle this without serializing the whole runtime state, like raising an exception in the middle of executing the function, but what are the chances that people will handle this correctly? How many command-line scripts have you used that do something really weird when you pressed &amp;lt;code&amp;gt;CTRL+C&amp;lt;/code&amp;gt;?&lt;br /&gt;
&lt;br /&gt;
To keep things simple for creators, we wanted to keep this nice property of LSL in whatever language we chose. This seemingly simple feature requires a lot of work because, think about it, how many programming languages let you seamlessly halt a program in the middle of it doing something, move it over to another computer, then resume whatever it was doing? Can you do that without doing a full-memory snapshot that would be gigabytes in size? That&#039;s not really a thing that people generally do, but it&#039;s required for us to not have sharp edges on our script creation experience!&lt;br /&gt;
&lt;br /&gt;
We have a version of this in our Mono scripting engine, which was all written in-house. It works very well for LSL, but it has trouble with more complex structures. As it turns out, taking all the state of a running program, serializing it, then transparently deserializing it is a difficult problem, even with data that &amp;lt;i&amp;gt;looks&amp;lt;/i&amp;gt; simple.&lt;br /&gt;
&lt;br /&gt;
Suppose we have something like &amp;lt;code&amp;gt;Dictionary&amp;lt;SomeObject, int&amp;gt;&amp;lt;/code&amp;gt; that we use as a counter for how many times we&#039;ve seen each &amp;lt;code&amp;gt;SomeObject&amp;lt;/code&amp;gt;. What do you do if the &amp;lt;code&amp;gt;GetHashCode()&amp;lt;/code&amp;gt; bucketing changes after the script deserializes and the memory increases? Can you prevent that from happening? Will live &amp;lt;code&amp;gt;IEnumerator&amp;lt;/code&amp;gt;s still work correctly? How can you be sure of this? .NET makes no guarantees about any of this behavior, and good luck digging through the .NET source code to figure out if any of this is even the case.&lt;br /&gt;
&lt;br /&gt;
What about types that aren&#039;t marked &amp;lt;code&amp;gt;[Serializable]&amp;lt;/code&amp;gt;, including types in the standard library? Can people just not use those? Do we have to write special custom serializers for them? Could they just randomly break when the script is deserialized and we accept that as a consequence?&lt;br /&gt;
&lt;br /&gt;
As you can see, there are a lot of hidden sharp edges involved in transparently moving scripts across servers, and the general answer provided when you ask about doing this is &amp;quot;That&#039;s very hard, make users write more intricate scripts that explicitly handle their own serialization and save their own state.&amp;quot; This is fine if most of your users are professional programmers, not so if you have a mix of professional programmers and regular users who just want an object that counts how many people have clicked on their head.&lt;br /&gt;
&lt;br /&gt;
==== Wrapping it up ====&lt;br /&gt;
&lt;br /&gt;
This is a very small subset of the issues we ran into while trying to design a satisfactory C#-based scripting system that fits Second Life&#039;s needs. Seriously, there&#039;s about 20 pages of notes.&lt;br /&gt;
&lt;br /&gt;
Meanwhile, Lua makes most of these things easy. Want to halt a script mid-execution? Okay, set an interrupt handler. Want to serialize your script while it&#039;s running and have it move to another server? Okay, there are libraries that have existed for decades that can do this. Fable&#039;s save file system was based on this. Quite a lot of scripting engine integration problems we have that would rise to the level of extremely difficult and relatively novel Computer Science research in .NET (and by extension, C#) are relatively simple to do with Lua.&lt;br /&gt;
&lt;br /&gt;
=== Okay, but seriously, why Lua specifically? Because it&#039;s popular for games? Because &amp;lt;other platform&amp;gt; uses it? Why not &amp;lt;X&amp;gt;? ===&lt;br /&gt;
&lt;br /&gt;
Actually, no. Neither other users of Lua, or a specific love of the language had anything to do with the decision. If you prefer a different programming language, there&#039;s a good chance we considered it. We actually looked at quite a lot of different languages and their associated engines, including JavaScript and WebAssembly, Lua was considered fairly late in the process. The ones below are just a small subset of the ones that we looked at:&lt;br /&gt;
&lt;br /&gt;
[[File:Lsl_vm_comparison.png|1000px]]&lt;br /&gt;
&lt;br /&gt;
As you can see, Luau was the only scripting runtime that fulfilled all of our requirements for a scripting engine within Second Life, and we arrived at it quite late in the decision process. We wanted something that we knew could deliver a high-quality scripting experience to creators, and we believe that Lua can best provide it.&lt;br /&gt;
&lt;br /&gt;
=== I genuinely hate Lua and refuse to use it ===&lt;br /&gt;
&lt;br /&gt;
A lot of people feel the same about LSL! Kidding aside, this is a totally defensible position, Lua can be a quirky language. Yes, it&#039;s strange that array indices start at 1. Yes, sometimes you can forget that you join strings with &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt;. It can take some getting used to.&lt;br /&gt;
&lt;br /&gt;
A big part of the reason why we&#039;re using Luau is due to the quality of the scripting engine itself, not specifically the language. A lot of issues people had with older versions of Lua have been addressed in Luau, and ones that haven&#039;t been for one reason or another can be easily dealt with since we have such a high degree of control over the scripting engine now. Luau is very easy to modify. If we find that there are real quality-of-life issues with Lua that people run into while writing scripts, we can just change how it works, assuming the benefits outweigh the cost of breaking interoperability with Lua scripts from elsewhere.&lt;br /&gt;
&lt;br /&gt;
But hey, maybe you really still hate the idea of using Lua. That&#039;s fine! The nice thing about having Lua instead of just LSL is there are a number of languages that can compile down to Lua code that could potentially work in Second Life! People have done work to get TypeScript and C# compiling to Lua, and it works pretty well for them, and there&#039;s also more exotic languages like MoonScript that target Lua specifically. See this GitHub repo for a listing: https://github.com/hengestone/lua-languages&lt;br /&gt;
&lt;br /&gt;
If you&#039;re so inclined, you can go as far as to take the Luau transpiler and hack it up to support &amp;lt;code&amp;gt;!=&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;//&amp;lt;/code&amp;gt; comments and really whatever you want. So long as it can take that source and output it to standard Lua, you&#039;re good.&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=File:Lsl_vm_comparison.png&amp;diff=1216907</id>
		<title>File:Lsl vm comparison.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=File:Lsl_vm_comparison.png&amp;diff=1216907"/>
		<updated>2024-07-09T22:54:37Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1216906</id>
		<title>Lua FAQ</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1216906"/>
		<updated>2024-07-09T19:09:14Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
=== Can I keep using LSL? ===&lt;br /&gt;
&lt;br /&gt;
Yep! LSL should work exactly the same as before, and a lot of testing work has been done (and is being done) to ensure that is the case. Even if you’re an LSL pro and rely on things like &amp;lt;code&amp;gt;(some_list != [])&amp;lt;/code&amp;gt; for getting the list length, or strange things like &amp;lt;code&amp;gt;((largestring=&amp;quot;&amp;quot;) + largestring)&amp;lt;/code&amp;gt;, you’re fully covered. Those still work, and now have optimized implementations.&lt;br /&gt;
&lt;br /&gt;
In fact, LSL will get better than ever before, running faster, and using less memory! The generated bytecode for an LSL script in Lua is generally half the size of a Mono script, and most strings take up half as much memory by virtue of using UTF-8 instead of UTF-16.&lt;br /&gt;
&lt;br /&gt;
We’ve taken a lot of care in writing the new LSL compiler to make the scripting experience for people who prefer LSL better than it was before.&lt;br /&gt;
&lt;br /&gt;
=== Are you going to stop adding functions / events to LSL? ===&lt;br /&gt;
&lt;br /&gt;
In the general case, no. A big part of the work we’ve done is making it easy to share functions between LSL and Lua without much extra effort. For events and functions that can be reasonably exposed to LSL (i.e., they only use simple types that LSL has anyway) then LSL should still get those.&lt;br /&gt;
&lt;br /&gt;
For more advanced features, the idea is that there should still be a path for exposing them to LSL. Obviously, with Lua it’s possible to have object-oriented APIs that would not be possible in LSL, but those objects will generally be written in a way that calling a method on a “special” object we introduce in Lua will really boil down to a function call that can be expressed in LSL.&lt;br /&gt;
&lt;br /&gt;
Not a real example, but &amp;lt;code&amp;gt;some_object:giveInventoryList(&amp;quot;folder_name&amp;quot;, inventory_list)&amp;lt;/code&amp;gt; might really boil down to a &amp;lt;code&amp;gt;llGiveInventoryList(some_object_id, &amp;quot;folder_name&amp;quot;, &amp;quot;inventory_list&amp;quot;);&amp;lt;/code&amp;gt; call. In that way, most additional Lua functionality will be a fancy “easier” wrapper around functions shared with LSL!&lt;br /&gt;
&lt;br /&gt;
The caveat is if functions are added that use new types that aren&#039;t in LSL (like functions that take in a &amp;lt;code&amp;gt;key -&amp;gt; value&amp;lt;/code&amp;gt; map) likely won&#039;t be exposed to LSL, as there&#039;s no way to represent them in LSL anyway.&lt;br /&gt;
&lt;br /&gt;
=== Wait, LSL can run on Lua now? How? Why would you do that? ===&lt;br /&gt;
&lt;br /&gt;
A reasonable question! It sounds crazy at first, but as it turns out, LSL maps really well to Lua bytecode, and we already have a compiler that can output both Mono and legacy LSO2 bytecode. Writing a compiler that could output Lua bytecode wasn&#039;t terribly difficult, and it allows LSL scripts to run faster, and be more memory-efficient. Most importantly, it allows us to test behavior of the new scripting engine against the behavior of the existing Mono scripting engine. This helps us make sure we&#039;re doing an apples-to-apples comparison on our benchmarks, and that all the nice things that Mono currently gets us will also work correctly under the new scripting engine.&lt;br /&gt;
&lt;br /&gt;
Additionally, for better or for worse, LSL is here to stay. People have been writing scripts in it for 20 years, and they&#039;re going to be running on the servers for years to come. Performance and memory usage issues with LSL scripts will continue to matter for the foreseeable future. The faster the server can run existing LSL scripts, the more time it can dedicate to running newer Lua scripts. Memory usage has been a particular problem with the existing Mono scripting engine, and this would help us address this.&lt;br /&gt;
&lt;br /&gt;
=== What about running scripts on the viewer-side if I want viewer-side effects? Does this have any interaction with the work adding Lua addons into the viewer? ===&lt;br /&gt;
&lt;br /&gt;
No, the work on adding Lua addons to the viewer is a separate effort. Lua scripts on SL will strictly run on the region, and not on the people&#039;s viewers. We&#039;re focused on providing a high-quality replacement to the existing server-side scripts before we look at anything else.&lt;br /&gt;
&lt;br /&gt;
Note that we are all too aware of the current difficulty of scripting HUD UIs for Second Life (we make a lot of our own) and we&#039;re thinking of ways we can make things better, but there are no definite plans, and it wouldn&#039;t involve running scripts on the viewer in the near-term. The current UI tooling exposed to Lua viewer addons isn&#039;t tooled for being called from untrusted code either way, so lack of &amp;quot;client-side&amp;quot; scripts is unlikely to hinder any effort to create a better API for HUDs for in-world systems.&lt;br /&gt;
&lt;br /&gt;
=== You said Lua runs LSL faster than Mono? It uses less memory? How? I&#039;m suspicious. ===&lt;br /&gt;
&lt;br /&gt;
A reasonable suspicion! We were also suspicious and suspected that Lua would be slightly slower, but in reality, yes, Lua can run LSL scripts quite a bit faster than the custom-patched Mono 2.6 we currently use. The best LSL on Mono has performed compared to Luau in our internal tests is 10% slower than Luau. Similarly, most LSL on Luau scripts end up being around half the size of Mono scripts. That means much more memory to play with in your LSL scripts.&lt;br /&gt;
&lt;br /&gt;
The reason why isn&#039;t intuitive, after all, Mono is a lot more intricate and has a lot of fancy performance optimizations like a JIT optimizer, but it becomes clear when you look at the bytecode that&#039;s generated for LSL on each scripting engine.&lt;br /&gt;
&lt;br /&gt;
Consider the following LSL function as an example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lsl2&amp;quot;&amp;gt;&lt;br /&gt;
float loudSubtract(float val, float sub) {&lt;br /&gt;
    // subtract, telling the owner what we did&lt;br /&gt;
	float new_val = val - sub;&lt;br /&gt;
	llOwnerSay(&amp;quot;New val is &amp;quot; + (string)new_val);&lt;br /&gt;
	return new_val;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This compiles to the following Luau bytecode (subject to further optimization):&lt;br /&gt;
&lt;br /&gt;
{| role=&amp;quot;presentation&amp;quot; class=&amp;quot;wikitable mw-collapsible autocollapse mw-collapsed&amp;quot;&lt;br /&gt;
| &amp;lt;strong&amp;gt;Lua bytecode&amp;lt;/strong&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre&amp;gt;&lt;br /&gt;
Function 0 (_floudSubtract):&lt;br /&gt;
SUB R2 R0 R1&lt;br /&gt;
GETIMPORT R3 2 [ll.OwnerSay]&lt;br /&gt;
GETIMPORT R6 5 [lsl.cast]&lt;br /&gt;
MOVE R7 R2&lt;br /&gt;
LOADN R8 3&lt;br /&gt;
CALL R6 2 1&lt;br /&gt;
LOADK R5 K6 [&#039;New val is &#039;]&lt;br /&gt;
CONCAT R4 R5 R6&lt;br /&gt;
CALL R3 1 0&lt;br /&gt;
RETURN R2 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Okay, that&#039;s pretty decent. Some places where things could be more optimized, but no big deal. The equivalent Mono bytecode is (and bear with us, this is long:)&lt;br /&gt;
&lt;br /&gt;
{| role=&amp;quot;presentation&amp;quot; class=&amp;quot;wikitable mw-collapsible autocollapse mw-collapsed&amp;quot;&lt;br /&gt;
| &amp;lt;strong&amp;gt;Mono bytecode&amp;lt;/strong&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre&amp;gt;&lt;br /&gt;
 .class public auto ansi serializable beforefieldinit&lt;br /&gt;
  LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    extends [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 &#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 val&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; l1&#039;&lt;br /&gt;
&lt;br /&gt;
  .method compilercontrolled specialname rtspecialname instance void&lt;br /&gt;
    .ctor(&lt;br /&gt;
      int32 _param1,&lt;br /&gt;
      class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 _param2,&lt;br /&gt;
      float32 _param3,&lt;br /&gt;
      float32 _param4,&lt;br /&gt;
      float32 _param5,&lt;br /&gt;
      int32 _param6&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 4&lt;br /&gt;
&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: call         instance void [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame::.ctor()&lt;br /&gt;
&lt;br /&gt;
    // [29 5 - 29 28]&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldarg.s      _param1&lt;br /&gt;
    IL_0009: nop&lt;br /&gt;
    IL_000a: nop&lt;br /&gt;
    IL_000b: nop&lt;br /&gt;
    IL_000c: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
    // [30 5 - 30 34]&lt;br /&gt;
    IL_0011: ldarg.0      // this&lt;br /&gt;
    IL_0012: ldarg.s      _param2&lt;br /&gt;
    IL_0014: nop&lt;br /&gt;
    IL_0015: nop&lt;br /&gt;
    IL_0016: nop&lt;br /&gt;
    IL_0017: stfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 23]&lt;br /&gt;
    IL_001c: ldarg.0      // this&lt;br /&gt;
    IL_001d: ldarg.s      _param3&lt;br /&gt;
    IL_001f: nop&lt;br /&gt;
    IL_0020: nop&lt;br /&gt;
    IL_0021: nop&lt;br /&gt;
    IL_0022: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
&lt;br /&gt;
    // [32 5 - 32 23]&lt;br /&gt;
    IL_0027: ldarg.0      // this&lt;br /&gt;
    IL_0028: ldarg.s      _param4&lt;br /&gt;
    IL_002a: nop&lt;br /&gt;
    IL_002b: nop&lt;br /&gt;
    IL_002c: nop&lt;br /&gt;
    IL_002d: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
    // [33 5 - 33 28]&lt;br /&gt;
    IL_0032: ldarg.0      // this&lt;br /&gt;
    IL_0033: ldarg.s      _param5&lt;br /&gt;
    IL_0035: nop&lt;br /&gt;
    IL_0036: nop&lt;br /&gt;
    IL_0037: nop&lt;br /&gt;
    IL_0038: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
    // [34 5 - 34 28]&lt;br /&gt;
    IL_003d: ldarg.0      // this&lt;br /&gt;
    IL_003e: ldarg.s      _param6&lt;br /&gt;
    IL_0040: nop&lt;br /&gt;
    IL_0041: nop&lt;br /&gt;
    IL_0042: nop&lt;br /&gt;
    IL_0043: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0048: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor&lt;br /&gt;
&lt;br /&gt;
  .method public virtual instance object&lt;br /&gt;
    Resume() cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 8&lt;br /&gt;
&lt;br /&gt;
    // [37 37 - 37 99]&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: ldfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
    IL_000c: ldarg.0      // this&lt;br /&gt;
    IL_000d: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
    IL_0012: call         instance float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575::gloudSubtract(float32, float32)&lt;br /&gt;
    IL_0017: box          [mscorlib]System.Single&lt;br /&gt;
    IL_001c: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::Resume&lt;br /&gt;
} // end of class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 // ... Within the actual script class&lt;br /&gt;
&lt;br /&gt;
 .method public hidebysig instance float32&lt;br /&gt;
    gloudSubtract(&lt;br /&gt;
      float32 val,&lt;br /&gt;
      float32 &#039;sub&#039;&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 16&lt;br /&gt;
    .locals init (&lt;br /&gt;
      [0] float32 num1,&lt;br /&gt;
      [1] int32 &#039;l1[34-35], num2[69-151], num2[151-152]&#039;,&lt;br /&gt;
      [2] class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame c9eec9e37575gloudSubtractFrame,&lt;br /&gt;
      [3] float32 num3&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    // [17 5 - 17 60]&lt;br /&gt;
    IL_0000: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsRestoring()&lt;br /&gt;
    IL_0005: brfalse      IL_0036&lt;br /&gt;
&lt;br /&gt;
    // [19 7 - 19 202]&lt;br /&gt;
    IL_000a: call         class [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame [UThread]LindenLab.SecondLife.UThread.UThread::Pop()&lt;br /&gt;
    IL_000f: castclass    LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0014: stloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
    // [20 7 - 20 53]&lt;br /&gt;
    IL_0015: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0016: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
    IL_001b: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [21 7 - 21 55]&lt;br /&gt;
    IL_001c: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_001d: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0022: stloc.1      // l1&lt;br /&gt;
&lt;br /&gt;
    // [22 7 - 22 55]&lt;br /&gt;
    IL_0023: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0024: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
    IL_0029: switch       (IL_0085, IL_004b)&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 58]&lt;br /&gt;
    IL_0036: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_003b: brfalse      IL_004b&lt;br /&gt;
&lt;br /&gt;
    // [33 7 - 33 15]&lt;br /&gt;
    IL_0040: ldc.i4       1 // 0x00000001&lt;br /&gt;
    IL_0045: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [34 7 - 34 20]&lt;br /&gt;
    IL_0046: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [37 5 - 37 70]&lt;br /&gt;
    IL_004b: ldarg.s      &#039;sub&#039;&lt;br /&gt;
    IL_004d: nop&lt;br /&gt;
    IL_004e: nop&lt;br /&gt;
    IL_004f: nop&lt;br /&gt;
    IL_0050: ldarg.s      val&lt;br /&gt;
    IL_0052: nop&lt;br /&gt;
    IL_0053: nop&lt;br /&gt;
    IL_0054: nop&lt;br /&gt;
    IL_0055: call         float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&lt;br /&gt;
    IL_005a: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [38 5 - 38 84]&lt;br /&gt;
    IL_005b: ldloc.0      // num1&lt;br /&gt;
    IL_005c: call         string [LslLibrary]LindenLab.SecondLife.LslRunTime::ToString(float32)&lt;br /&gt;
    IL_0061: ldstr        &amp;quot;New val is &amp;quot;&lt;br /&gt;
    IL_0066: call         string [LslUserScript]LindenLab.SecondLife.LslUserScript::Add(string, string)&lt;br /&gt;
    IL_006b: call         void [LslLibrary]LindenLab.SecondLife.Library::llOwnerSay(string)&lt;br /&gt;
&lt;br /&gt;
    // [39 5 - 39 58]&lt;br /&gt;
    IL_0070: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_0075: brfalse      IL_0085&lt;br /&gt;
&lt;br /&gt;
    // [41 7 - 41 15]&lt;br /&gt;
    IL_007a: ldc.i4       0 // 0x00000000&lt;br /&gt;
    IL_007f: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [42 7 - 42 20]&lt;br /&gt;
    IL_0080: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [45 5 - 45 17]&lt;br /&gt;
    IL_0085: ldloc.0      // num1&lt;br /&gt;
    IL_0086: ret&lt;br /&gt;
    IL_0087: nop&lt;br /&gt;
&lt;br /&gt;
    // [47 5 - 47 194]&lt;br /&gt;
    IL_0088: ldloc.1      // num2&lt;br /&gt;
    IL_0089: ldarg.0      // this&lt;br /&gt;
    IL_008a: ldarg        val&lt;br /&gt;
    IL_008e: nop&lt;br /&gt;
    IL_008f: nop&lt;br /&gt;
    IL_0090: ldarg        &#039;sub&#039;&lt;br /&gt;
    IL_0094: nop&lt;br /&gt;
    IL_0095: nop&lt;br /&gt;
    IL_0096: ldloc.0      // num1&lt;br /&gt;
    IL_0097: ldloc.1      // num2&lt;br /&gt;
    IL_0098: newobj       instance void LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor(int32, class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575, float32, float32, float32, int32)&lt;br /&gt;
    IL_009d: pop&lt;br /&gt;
&lt;br /&gt;
    // [49 5 - 49 17]&lt;br /&gt;
    IL_009e: ldloc.3      // num3&lt;br /&gt;
    IL_009f: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
While this might look unreasonably large, this is also pretty similar to how &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt; C# functions that have &amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt;s get compiled. The code required to allow a function to pause in the middle to let other things run is necessarily very large, and our Mono bytecode instrumentation is essentially a special variant of that which optimizes for cases where the function won&#039;t have to pause. Try pasting the following code into sharplab.io (while decompiling to IL) and you&#039;ll see something very similar:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
using System.Threading.Tasks;&lt;br /&gt;
public class Example {&lt;br /&gt;
    public async Task llOwnerSay(string val)&lt;br /&gt;
    {&lt;br /&gt;
        // stub&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    public async Task&amp;lt;float&amp;gt; loudSubtract(float val, float sub) {&lt;br /&gt;
        float new_val = val - sub;&lt;br /&gt;
        await llOwnerSay(&amp;quot;New val is &amp;quot; + new_val.ToString());&lt;br /&gt;
        return new_val;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Lua bytecode ends up being a lot smaller, because the Lua scripting engine itself knows how to handle this case, and doesn&#039;t need to add any special code to your scripts to allow them to be paused and resumed in the middle of a function.&lt;br /&gt;
&lt;br /&gt;
Even though Mono has a JIT optimizer, which should in theory make the script run faster, this sort of code is very unusual, and the JIT optimizer isn&#039;t able to optimize it very well. That means that even though Luau itself is in theory slower, it&#039;s able to run LSL much faster than Mono can. &lt;br /&gt;
&lt;br /&gt;
You might also notice things like call &amp;lt;code&amp;gt;float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&amp;lt;/code&amp;gt; in the Mono code. Those are necessary because of LSL&#039;s weird operand evaluation order, but are unnecessary in Luau because of the more efficient form of bytecode that Luau uses.&lt;br /&gt;
&lt;br /&gt;
In general, C# doesn&#039;t aim at producing very small bytecode for async functions, which is important for something like Second Life where you might have thousands of different individual scripts running within a region. Luau does generate small bytecode, while still having decent performance, making it a good choice for Second Life. And hey, strings are UTF-8 in Lua instead of UTF-16 as in Mono, so they use much less memory for most data as well.&lt;br /&gt;
&lt;br /&gt;
=== Are we going to get a higher memory limit in scripts? ===&lt;br /&gt;
&lt;br /&gt;
Not at first, Luau uses less memory than Mono, so you shouldn&#039;t really miss it. You&#039;re not going to start hitting script memory limits just because you converted your script to Lua. Using Lua proper should allow you to more precisely control memory usage in your scripts, e.g. by being able to handle memory allocation errors in &amp;lt;code&amp;gt;pcall()&amp;lt;/code&amp;gt; and access to the mutable &amp;lt;code&amp;gt;buffer&amp;lt;/code&amp;gt; class for byte-packing.&lt;br /&gt;
&lt;br /&gt;
There is some interest in giving specific scripts a little more memory if they&#039;re designated &amp;quot;high priority&amp;quot;, like a main &amp;quot;game manager&amp;quot; object for an experience, but there are no specific plans for this at the moment.&lt;br /&gt;
&lt;br /&gt;
=== Are my existing LSL scripts going to run on Lua Automatically? ===&lt;br /&gt;
&lt;br /&gt;
While we can run LSL in Lua now, and the idea is to eventually phase out Mono, there’s no immediate plans to transparently migrate existing scripts over to Lua. There are ideas about how this could be done safely without breaking existing content, but we want to make sure we have everything right before we start work on this.&lt;br /&gt;
&lt;br /&gt;
=== How does interoperation with LSL work? ===&lt;br /&gt;
&lt;br /&gt;
Everything that you expect from LSL should have some equivalent in Lua. There’s &amp;lt;code&amp;gt;key&amp;lt;/code&amp;gt;s, &amp;lt;code&amp;gt;rotation&amp;lt;/code&amp;gt;s and &amp;lt;code&amp;gt;vector&amp;lt;/code&amp;gt;s and something like &amp;lt;code&amp;gt;some_vec * some_rot&amp;lt;/code&amp;gt; in LSL is the exact same in Lua scripts.&lt;br /&gt;
&lt;br /&gt;
All LSL functions will be available in Lua, and something like &amp;lt;code&amp;gt;llSay(0, “hi!”)&amp;lt;/code&amp;gt; is just &amp;lt;code&amp;gt;ll.Say(0, “hi!”)&amp;lt;/code&amp;gt; now. What about &amp;lt;code&amp;gt;llSetPos(llGetPos() + &amp;lt;0, 0, 1&amp;gt;);&amp;lt;/code&amp;gt;? Just &amp;lt;code&amp;gt;ll.SetPos(ll.GetPos() + vector(0, 0, 1))&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
No special magic required here, and you can write whatever fancy wrappers you want around the existing LSL functions. We’ll be looking at how residents actually write these wrappers to inform the design of “official” higher-level Lua APIs, so we’re excited to see your ideas!&lt;br /&gt;
&lt;br /&gt;
=== Can I mix LSL and Lua in the same script? ===&lt;br /&gt;
&lt;br /&gt;
No, although most things should be a direct translation. At some point we may make a tool to help you do this, but there are no specific plans at the moment.&lt;br /&gt;
&lt;br /&gt;
If your object has multiple scripts, you can mix LSL and Lua within the same object, though.&lt;br /&gt;
&lt;br /&gt;
=== Can we use all of Lua? Is anything not there? ===&lt;br /&gt;
&lt;br /&gt;
Most everything will be there, except for two notable exceptions:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;getfenv()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;setfenv()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;_ENV&amp;lt;/code&amp;gt; are not accessible because they necessarily make the script harder to optimize. Usually people only use this for mocking in test code, and this can be accomplished by passing in a parameter with a table of function references rather than pretending you have different global variable values for certain functions.&lt;br /&gt;
* &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; is not supported since it has a huge impact on performance. For those not familiar, this is equivalent to eval() in other languages. Every single time you call &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; the compiler has to go over what you passed in, compile it, and then load the generated bytecode. The Lua compiler isn’t particularly fast, and doing this can cause your script to halt in a way that we can’t interrupt to give other scripts time to run. This is a big part of why &amp;lt;code&amp;gt;eval()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; are generally considered poor programming practice.&lt;br /&gt;
&lt;br /&gt;
The one real use-case we could think of for &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; is a debugger, and we’re looking at implementing a “real” debugger that lets you step through your code. Note that this won’t happen with the initial release, but watch this space!&lt;br /&gt;
&lt;br /&gt;
One thing that is planned, but will likely not be in the initial release is the coroutine library. This is one we’d really like to have in there, so you can do fancy things like have multiple “timers” in a script by just having coroutines that sleep every so often, but multiple running coroutines within the same script doesn’t fit well with the existing scripting architecture the Second Life server uses. Rest assured, this will be in there soon™ after Lua is released, but in the meantime you can still use something like the &amp;lt;code&amp;gt;Promise&amp;lt;/code&amp;gt; chaining that JavaScript uses to write asynchronous event handlers.&lt;br /&gt;
&lt;br /&gt;
We’ll have code demonstrating &amp;quot;proper&amp;quot; usage of Lua around the time that the public beta starts.&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1216877</id>
		<title>Lua FAQ</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1216877"/>
		<updated>2024-07-03T16:56:43Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
=== Can I keep using LSL? ===&lt;br /&gt;
&lt;br /&gt;
Yep! LSL should work exactly the same as before, and a lot of testing work has been done (and is being done) to ensure that is the case. Even if you’re an LSL pro and rely on things like &amp;lt;code&amp;gt;(some_list != [])&amp;lt;/code&amp;gt; for getting the list length, or strange things like &amp;lt;code&amp;gt;((largestring=&amp;quot;&amp;quot;) + largestring)&amp;lt;/code&amp;gt;, you’re fully covered. Those still work, and now have optimized implementations.&lt;br /&gt;
&lt;br /&gt;
In fact, LSL will get better than ever before, running faster, and using less memory! The generated bytecode for an LSL script in Lua is generally half the size of a Mono script, and most strings take up half as much memory by virtue of using UTF-8 instead of UTF-16.&lt;br /&gt;
&lt;br /&gt;
We’ve taken a lot of care in writing the new LSL compiler to make the scripting experience for people who prefer LSL better than it was before.&lt;br /&gt;
&lt;br /&gt;
=== Are you going to stop adding functions / events to LSL? ===&lt;br /&gt;
&lt;br /&gt;
In the general case, no. A big part of the work we’ve done is making it easy to share functions between LSL and Lua without much extra effort. For events and functions that can be reasonably exposed to LSL (i.e., they only use simple types that LSL has anyway) then LSL should still get those.&lt;br /&gt;
&lt;br /&gt;
For more advanced features, the idea is that there should still be a path for exposing them to LSL. Obviously, with Lua it’s possible to have object-oriented APIs that would not be possible in LSL, but those objects will generally be written in a way that calling a method on a “special” object we introduce in Lua will really boil down to a function call that can be expressed in LSL.&lt;br /&gt;
&lt;br /&gt;
Not a real example, but &amp;lt;code&amp;gt;some_object:giveInventoryList(&amp;quot;folder_name&amp;quot;, inventory_list)&amp;lt;/code&amp;gt; might really boil down to a &amp;lt;code&amp;gt;llGiveInventoryList(some_object_id, &amp;quot;folder_name&amp;quot;, &amp;quot;inventory_list&amp;quot;);&amp;lt;/code&amp;gt; call. In that way, most additional Lua functionality will be a fancy “easier” wrapper around functions shared with LSL!&lt;br /&gt;
&lt;br /&gt;
The caveat is if functions are added that use new types that aren&#039;t in LSL (like functions that take in a &amp;lt;code&amp;gt;key -&amp;gt; value&amp;lt;/code&amp;gt; map) likely won&#039;t be exposed to LSL, as there&#039;s no way to represent them in LSL anyway.&lt;br /&gt;
&lt;br /&gt;
=== Wait, LSL can run on Lua now? How? Why would you do that? ===&lt;br /&gt;
&lt;br /&gt;
A reasonable question! It sounds crazy at first, but as it turns out, LSL maps really well to Lua bytecode, and we already have a compiler that can output both Mono and legacy LSO2 bytecode. Writing a compiler that could output Lua bytecode wasn&#039;t terribly difficult, and it allows LSL scripts to run faster, and be more memory-efficient. Most importantly, it allows us to test behavior of the new scripting engine against the behavior of the existing Mono scripting engine. This helps us make sure we&#039;re doing an apples-to-apples comparison on our benchmarks, and that all the nice things that Mono currently gets us will also work correctly under the new scripting engine.&lt;br /&gt;
&lt;br /&gt;
Additionally, for better or for worse, LSL is here to stay. People have been writing scripts in it for 20 years, and they&#039;re going to be running on the servers for years to come. Performance and memory usage issues with LSL scripts will continue to matter for the foreseeable future. The faster the server can run existing LSL scripts, the more time it can dedicate to running newer Lua scripts. Memory usage has been a particular problem with the existing Mono scripting engine, and this would help us address this.&lt;br /&gt;
&lt;br /&gt;
=== What about running scripts on the viewer-side if I want viewer-side effects? Does this have any interaction with the work adding Lua addons into the viewer? ===&lt;br /&gt;
&lt;br /&gt;
No, the work on adding Lua addons to the viewer is a separate effort. Lua scripts on SL will strictly run on the region, and not on the people&#039;s viewers. We&#039;re focused on providing a high-quality replacement to the existing server-side scripts before we look at anything else.&lt;br /&gt;
&lt;br /&gt;
Note that we are all too aware of the current difficulty of scripting HUD UIs for Second Life (we make a lot of our own) and we&#039;re thinking of ways we can make things better, but there are no definite plans, and it wouldn&#039;t involve running scripts on the viewer in the near-term. The current UI tooling exposed to Lua viewer addons isn&#039;t tooled for being called from untrusted code either way, so lack of &amp;quot;client-side&amp;quot; scripts is unlikely to hinder any effort to create a better API for HUDs for in-world systems.&lt;br /&gt;
&lt;br /&gt;
=== You said Lua runs LSL faster than Mono? It uses less memory? How? I&#039;m suspicious. ===&lt;br /&gt;
&lt;br /&gt;
A reasonable suspicion! We were also suspicious and suspected that Lua would be slightly slower, but in reality, yes, Lua can run LSL scripts quite a bit faster than the custom-patched Mono 2.6 we currently use. The best LSL on Mono has performed compared to Luau in our internal tests is 10% slower than Luau. Similarly, most LSL on Luau scripts end up being around half the size of Mono scripts. That means much more memory to play with in your LSL scripts.&lt;br /&gt;
&lt;br /&gt;
The reason why isn&#039;t intuitive, after all, Mono is a lot more intricate and has a lot of fancy performance optimizations like a JIT optimizer, but it becomes clear when you look at the bytecode that&#039;s generated for LSL on each scripting engine.&lt;br /&gt;
&lt;br /&gt;
Consider the following LSL function as an example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lsl2&amp;quot;&amp;gt;&lt;br /&gt;
float loudSubtract(float val, float sub) {&lt;br /&gt;
    // subtract, telling the owner what we did&lt;br /&gt;
	float new_val = val - sub;&lt;br /&gt;
	llOwnerSay(&amp;quot;New val is &amp;quot; + (string)new_val);&lt;br /&gt;
	return new_val;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This compiles to the following Luau bytecode (subject to further optimization):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Function 0 (_floudSubtract):&lt;br /&gt;
SUB R2 R0 R1&lt;br /&gt;
GETIMPORT R3 2 [ll.OwnerSay]&lt;br /&gt;
GETIMPORT R6 5 [lsl.cast]&lt;br /&gt;
MOVE R7 R2&lt;br /&gt;
LOADN R8 3&lt;br /&gt;
CALL R6 2 1&lt;br /&gt;
LOADK R5 K6 [&#039;New val is &#039;]&lt;br /&gt;
CONCAT R4 R5 R6&lt;br /&gt;
CALL R3 1 0&lt;br /&gt;
RETURN R2 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Okay, that&#039;s pretty decent. Some places where things could be more optimized, but no big deal. The equivalent Mono bytecode is (and bear with us, this is long:)&lt;br /&gt;
&lt;br /&gt;
{| role=&amp;quot;presentation&amp;quot; class=&amp;quot;wikitable mw-collapsible autocollapse mw-collapsed&amp;quot;&lt;br /&gt;
| &amp;lt;strong&amp;gt;Mono bytecode&amp;lt;/strong&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre&amp;gt;&lt;br /&gt;
 .class public auto ansi serializable beforefieldinit&lt;br /&gt;
  LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    extends [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 &#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 val&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; l1&#039;&lt;br /&gt;
&lt;br /&gt;
  .method compilercontrolled specialname rtspecialname instance void&lt;br /&gt;
    .ctor(&lt;br /&gt;
      int32 _param1,&lt;br /&gt;
      class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 _param2,&lt;br /&gt;
      float32 _param3,&lt;br /&gt;
      float32 _param4,&lt;br /&gt;
      float32 _param5,&lt;br /&gt;
      int32 _param6&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 4&lt;br /&gt;
&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: call         instance void [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame::.ctor()&lt;br /&gt;
&lt;br /&gt;
    // [29 5 - 29 28]&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldarg.s      _param1&lt;br /&gt;
    IL_0009: nop&lt;br /&gt;
    IL_000a: nop&lt;br /&gt;
    IL_000b: nop&lt;br /&gt;
    IL_000c: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
    // [30 5 - 30 34]&lt;br /&gt;
    IL_0011: ldarg.0      // this&lt;br /&gt;
    IL_0012: ldarg.s      _param2&lt;br /&gt;
    IL_0014: nop&lt;br /&gt;
    IL_0015: nop&lt;br /&gt;
    IL_0016: nop&lt;br /&gt;
    IL_0017: stfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 23]&lt;br /&gt;
    IL_001c: ldarg.0      // this&lt;br /&gt;
    IL_001d: ldarg.s      _param3&lt;br /&gt;
    IL_001f: nop&lt;br /&gt;
    IL_0020: nop&lt;br /&gt;
    IL_0021: nop&lt;br /&gt;
    IL_0022: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
&lt;br /&gt;
    // [32 5 - 32 23]&lt;br /&gt;
    IL_0027: ldarg.0      // this&lt;br /&gt;
    IL_0028: ldarg.s      _param4&lt;br /&gt;
    IL_002a: nop&lt;br /&gt;
    IL_002b: nop&lt;br /&gt;
    IL_002c: nop&lt;br /&gt;
    IL_002d: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
    // [33 5 - 33 28]&lt;br /&gt;
    IL_0032: ldarg.0      // this&lt;br /&gt;
    IL_0033: ldarg.s      _param5&lt;br /&gt;
    IL_0035: nop&lt;br /&gt;
    IL_0036: nop&lt;br /&gt;
    IL_0037: nop&lt;br /&gt;
    IL_0038: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
    // [34 5 - 34 28]&lt;br /&gt;
    IL_003d: ldarg.0      // this&lt;br /&gt;
    IL_003e: ldarg.s      _param6&lt;br /&gt;
    IL_0040: nop&lt;br /&gt;
    IL_0041: nop&lt;br /&gt;
    IL_0042: nop&lt;br /&gt;
    IL_0043: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0048: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor&lt;br /&gt;
&lt;br /&gt;
  .method public virtual instance object&lt;br /&gt;
    Resume() cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 8&lt;br /&gt;
&lt;br /&gt;
    // [37 37 - 37 99]&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: ldfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
    IL_000c: ldarg.0      // this&lt;br /&gt;
    IL_000d: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
    IL_0012: call         instance float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575::gloudSubtract(float32, float32)&lt;br /&gt;
    IL_0017: box          [mscorlib]System.Single&lt;br /&gt;
    IL_001c: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::Resume&lt;br /&gt;
} // end of class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 // ... Within the actual script class&lt;br /&gt;
&lt;br /&gt;
 .method public hidebysig instance float32&lt;br /&gt;
    gloudSubtract(&lt;br /&gt;
      float32 val,&lt;br /&gt;
      float32 &#039;sub&#039;&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 16&lt;br /&gt;
    .locals init (&lt;br /&gt;
      [0] float32 num1,&lt;br /&gt;
      [1] int32 &#039;l1[34-35], num2[69-151], num2[151-152]&#039;,&lt;br /&gt;
      [2] class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame c9eec9e37575gloudSubtractFrame,&lt;br /&gt;
      [3] float32 num3&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    // [17 5 - 17 60]&lt;br /&gt;
    IL_0000: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsRestoring()&lt;br /&gt;
    IL_0005: brfalse      IL_0036&lt;br /&gt;
&lt;br /&gt;
    // [19 7 - 19 202]&lt;br /&gt;
    IL_000a: call         class [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame [UThread]LindenLab.SecondLife.UThread.UThread::Pop()&lt;br /&gt;
    IL_000f: castclass    LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0014: stloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
    // [20 7 - 20 53]&lt;br /&gt;
    IL_0015: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0016: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
    IL_001b: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [21 7 - 21 55]&lt;br /&gt;
    IL_001c: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_001d: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0022: stloc.1      // l1&lt;br /&gt;
&lt;br /&gt;
    // [22 7 - 22 55]&lt;br /&gt;
    IL_0023: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0024: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
    IL_0029: switch       (IL_0085, IL_004b)&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 58]&lt;br /&gt;
    IL_0036: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_003b: brfalse      IL_004b&lt;br /&gt;
&lt;br /&gt;
    // [33 7 - 33 15]&lt;br /&gt;
    IL_0040: ldc.i4       1 // 0x00000001&lt;br /&gt;
    IL_0045: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [34 7 - 34 20]&lt;br /&gt;
    IL_0046: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [37 5 - 37 70]&lt;br /&gt;
    IL_004b: ldarg.s      &#039;sub&#039;&lt;br /&gt;
    IL_004d: nop&lt;br /&gt;
    IL_004e: nop&lt;br /&gt;
    IL_004f: nop&lt;br /&gt;
    IL_0050: ldarg.s      val&lt;br /&gt;
    IL_0052: nop&lt;br /&gt;
    IL_0053: nop&lt;br /&gt;
    IL_0054: nop&lt;br /&gt;
    IL_0055: call         float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&lt;br /&gt;
    IL_005a: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [38 5 - 38 84]&lt;br /&gt;
    IL_005b: ldloc.0      // num1&lt;br /&gt;
    IL_005c: call         string [LslLibrary]LindenLab.SecondLife.LslRunTime::ToString(float32)&lt;br /&gt;
    IL_0061: ldstr        &amp;quot;New val is &amp;quot;&lt;br /&gt;
    IL_0066: call         string [LslUserScript]LindenLab.SecondLife.LslUserScript::Add(string, string)&lt;br /&gt;
    IL_006b: call         void [LslLibrary]LindenLab.SecondLife.Library::llOwnerSay(string)&lt;br /&gt;
&lt;br /&gt;
    // [39 5 - 39 58]&lt;br /&gt;
    IL_0070: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_0075: brfalse      IL_0085&lt;br /&gt;
&lt;br /&gt;
    // [41 7 - 41 15]&lt;br /&gt;
    IL_007a: ldc.i4       0 // 0x00000000&lt;br /&gt;
    IL_007f: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [42 7 - 42 20]&lt;br /&gt;
    IL_0080: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [45 5 - 45 17]&lt;br /&gt;
    IL_0085: ldloc.0      // num1&lt;br /&gt;
    IL_0086: ret&lt;br /&gt;
    IL_0087: nop&lt;br /&gt;
&lt;br /&gt;
    // [47 5 - 47 194]&lt;br /&gt;
    IL_0088: ldloc.1      // num2&lt;br /&gt;
    IL_0089: ldarg.0      // this&lt;br /&gt;
    IL_008a: ldarg        val&lt;br /&gt;
    IL_008e: nop&lt;br /&gt;
    IL_008f: nop&lt;br /&gt;
    IL_0090: ldarg        &#039;sub&#039;&lt;br /&gt;
    IL_0094: nop&lt;br /&gt;
    IL_0095: nop&lt;br /&gt;
    IL_0096: ldloc.0      // num1&lt;br /&gt;
    IL_0097: ldloc.1      // num2&lt;br /&gt;
    IL_0098: newobj       instance void LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor(int32, class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575, float32, float32, float32, int32)&lt;br /&gt;
    IL_009d: pop&lt;br /&gt;
&lt;br /&gt;
    // [49 5 - 49 17]&lt;br /&gt;
    IL_009e: ldloc.3      // num3&lt;br /&gt;
    IL_009f: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
While this might look unreasonably large, this is also pretty similar to how &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt; C# functions that have &amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt;s get compiled. The code required to allow a function to pause in the middle to let other things run is necessarily very large, and our Mono bytecode instrumentation is essentially a special variant of that which optimizes for cases where the function won&#039;t have to pause. Try pasting the following code into sharplab.io (while decompiling to IL) and you&#039;ll see something very similar:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
using System.Threading.Tasks;&lt;br /&gt;
public class Example {&lt;br /&gt;
    public async Task llOwnerSay(string val)&lt;br /&gt;
    {&lt;br /&gt;
        // stub&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    public async Task&amp;lt;float&amp;gt; loudSubtract(float val, float sub) {&lt;br /&gt;
        float new_val = val - sub;&lt;br /&gt;
        await llOwnerSay(&amp;quot;New val is &amp;quot; + new_val.ToString());&lt;br /&gt;
        return new_val;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Lua bytecode ends up being a lot smaller, because the Lua scripting engine itself knows how to handle this case, and doesn&#039;t need to add any special code to your scripts to allow them to be paused and resumed in the middle of a function.&lt;br /&gt;
&lt;br /&gt;
Even though Mono has a JIT optimizer, which should in theory make the script run faster, this sort of code is very unusual, and the JIT optimizer isn&#039;t able to optimize it very well. That means that even though Luau itself is in theory slower, it&#039;s able to run LSL much faster than Mono can. &lt;br /&gt;
&lt;br /&gt;
You might also notice things like call &amp;lt;code&amp;gt;float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&amp;lt;/code&amp;gt; in the Mono code. Those are necessary because of LSL&#039;s weird operand evaluation order, but are unnecessary in Luau because of the more efficient form of bytecode that Luau uses.&lt;br /&gt;
&lt;br /&gt;
In general, C# doesn&#039;t aim at producing very small bytecode for async functions, which is important for something like Second Life where you might have thousands of different individual scripts running within a region. Luau does generate small bytecode, while still having decent performance, making it a good choice for Second Life. And hey, strings are UTF-8 in Lua instead of UTF-16 as in Mono, so they use much less memory for most data as well.&lt;br /&gt;
&lt;br /&gt;
=== Are we going to get a higher memory limit in scripts? ===&lt;br /&gt;
&lt;br /&gt;
Not at first, Luau uses less memory than Mono, so you shouldn&#039;t really miss it. You&#039;re not going to start hitting script memory limits just because you converted your script to Lua. Using Lua proper should allow you to more precisely control memory usage in your scripts, e.g. by being able to handle memory allocation errors in &amp;lt;code&amp;gt;pcall()&amp;lt;/code&amp;gt; and access to the mutable &amp;lt;code&amp;gt;buffer&amp;lt;/code&amp;gt; class for byte-packing.&lt;br /&gt;
&lt;br /&gt;
There is some interest in giving specific scripts a little more memory if they&#039;re designated &amp;quot;high priority&amp;quot;, like a main &amp;quot;game manager&amp;quot; object for an experience, but there are no specific plans for this at the moment.&lt;br /&gt;
&lt;br /&gt;
=== Are my existing LSL scripts going to run on Lua Automatically? ===&lt;br /&gt;
&lt;br /&gt;
While we can run LSL in Lua now, and the idea is to eventually phase out Mono, there’s no immediate plans to transparently migrate existing scripts over to Lua. There are ideas about how this could be done safely without breaking existing content, but we want to make sure we have everything right before we start work on this.&lt;br /&gt;
&lt;br /&gt;
=== How does interoperation with LSL work? ===&lt;br /&gt;
&lt;br /&gt;
Everything that you expect from LSL should have some equivalent in Lua. There’s &amp;lt;code&amp;gt;key&amp;lt;/code&amp;gt;s, &amp;lt;code&amp;gt;rotation&amp;lt;/code&amp;gt;s and &amp;lt;code&amp;gt;vector&amp;lt;/code&amp;gt;s and something like &amp;lt;code&amp;gt;some_vec * some_rot&amp;lt;/code&amp;gt; in LSL is the exact same in Lua scripts.&lt;br /&gt;
&lt;br /&gt;
All LSL functions will be available in Lua, and something like &amp;lt;code&amp;gt;llSay(0, “hi!”)&amp;lt;/code&amp;gt; is just &amp;lt;code&amp;gt;ll.Say(0, “hi!”)&amp;lt;/code&amp;gt; now. What about &amp;lt;code&amp;gt;llSetPos(llGetPos() + &amp;lt;0, 0, 1&amp;gt;);&amp;lt;/code&amp;gt;? Just &amp;lt;code&amp;gt;ll.SetPos(ll.GetPos() + vector(0, 0, 1))&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
No special magic required here, and you can write whatever fancy wrappers you want around the existing LSL functions. We’ll be looking at how residents actually write these wrappers to inform the design of “official” higher-level Lua APIs, so we’re excited to see your ideas!&lt;br /&gt;
&lt;br /&gt;
=== Can I mix LSL and Lua in the same script? ===&lt;br /&gt;
&lt;br /&gt;
No, although most things should be a direct translation. At some point we may make a tool to help you do this, but there are no specific plans at the moment.&lt;br /&gt;
&lt;br /&gt;
If your object has multiple scripts, you can mix LSL and Lua within the same object, though.&lt;br /&gt;
&lt;br /&gt;
=== Can we use all of Lua? Is anything not there? ===&lt;br /&gt;
&lt;br /&gt;
Most everything will be there, except for two notable exceptions:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;getfenv()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;setfenv()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;_ENV&amp;lt;/code&amp;gt; are not accessible because they necessarily make the script harder to optimize. Usually people only use this for mocking in test code, and this can be accomplished by passing in a parameter with a table of function references rather than pretending you have different global variable values for certain functions.&lt;br /&gt;
* &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; is not supported since it has a huge impact on performance. For those not familiar, this is equivalent to eval() in other languages. Every single time you call &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; the compiler has to go over what you passed in, compile it, and then load the generated bytecode. The Lua compiler isn’t particularly fast, and doing this can cause your script to halt in a way that we can’t interrupt to give other scripts time to run. This is a big part of why &amp;lt;code&amp;gt;eval()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; are generally considered poor programming practice.&lt;br /&gt;
&lt;br /&gt;
The one real use-case we could think of for &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; is a debugger, and we’re looking at implementing a “real” debugger that lets you step through your code. Note that this won’t happen with the initial release, but watch this space!&lt;br /&gt;
&lt;br /&gt;
One thing that is planned, but will likely not be in the initial release is the coroutine library. This is one we’d really like to have in there, so you can do fancy things like have multiple “timers” in a script by just having coroutines that sleep every so often, but multiple running coroutines within the same script doesn’t fit well with the existing scripting architecture the Second Life server uses. Rest assured, this will be in there soon™ after Lua is released, but in the meantime you can still use something like the &amp;lt;code&amp;gt;Promise&amp;lt;/code&amp;gt; chaining that JavaScript uses to write asynchronous event handlers.&lt;br /&gt;
&lt;br /&gt;
We’ll have code demonstrating &amp;quot;proper&amp;quot; usage of Lua around the time that the public beta starts.&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1216875</id>
		<title>Lua FAQ</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1216875"/>
		<updated>2024-07-02T20:01:29Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
=== Can I keep using LSL? ===&lt;br /&gt;
&lt;br /&gt;
Yep! LSL should work exactly the same as before, and a lot of testing work has been done (and is being done) to ensure that is the case. Even if you’re an LSL pro and rely on things like &amp;lt;code&amp;gt;(some_list != [])&amp;lt;/code&amp;gt; for getting the list length, or strange things like &amp;lt;code&amp;gt;((largestring=&amp;quot;&amp;quot;) + largestring)&amp;lt;/code&amp;gt;, you’re fully covered. Those still work, and now have optimized implementations.&lt;br /&gt;
&lt;br /&gt;
In fact, LSL will get better than ever before, running faster, and using less memory! The generated bytecode for an LSL script in Lua is generally half the size of a Mono script, and most strings take up half as much memory by virtue of using UTF-8 instead of UTF-16.&lt;br /&gt;
&lt;br /&gt;
We’ve taken a lot of care in writing the new LSL compiler to make the scripting experience for people who prefer LSL better than it was before.&lt;br /&gt;
&lt;br /&gt;
=== Are you going to stop adding functions / events to LSL? ===&lt;br /&gt;
&lt;br /&gt;
In the general case, no. A big part of the work we’ve done is making it easy to share functions between LSL and Lua without much extra effort. For events and functions that can be reasonably exposed to LSL (i.e., they only use simple types that LSL has anyway) then LSL should still get those.&lt;br /&gt;
&lt;br /&gt;
For more advanced features, the idea is that there should still be a path for exposing them to LSL. Obviously, with Lua it’s possible to have object-oriented APIs that would not be possible in LSL, but those objects will generally be written in a way that calling a method on a “special” object we introduce in Lua will really boil down to a function call that can be expressed in LSL.&lt;br /&gt;
&lt;br /&gt;
Not a real example, but &amp;lt;code&amp;gt;some_object:giveInventoryList(&amp;quot;folder_name&amp;quot;, inventory_list)&amp;lt;/code&amp;gt; might really boil down to a &amp;lt;code&amp;gt;llGiveInventoryList(some_object_id, &amp;quot;folder_name&amp;quot;, &amp;quot;inventory_list&amp;quot;);&amp;lt;/code&amp;gt; call. In that way, most additional Lua functionality will be a fancy “easier” wrapper around functions shared with LSL!&lt;br /&gt;
&lt;br /&gt;
The caveat is if functions are added that use new types that aren&#039;t in LSL (like functions that take in a &amp;lt;code&amp;gt;key -&amp;gt; value&amp;lt;/code&amp;gt; map) likely won&#039;t be exposed to LSL, as there&#039;s no way to represent them in LSL anyway.&lt;br /&gt;
&lt;br /&gt;
=== Wait, LSL can run on Lua now? How? Why would you do that? ===&lt;br /&gt;
&lt;br /&gt;
A reasonable question! It sounds crazy at first, but as it turns out, LSL maps really well to Lua bytecode, and we already have a compiler that can output both Mono and legacy LSO2 bytecode. Writing a compiler that could output Lua bytecode wasn&#039;t terribly difficult, and it allows LSL scripts to run faster, and be more memory-efficient. Most importantly, it allows us to test behavior of the new scripting engine against the behavior of the existing Mono scripting engine. This helps us make sure we&#039;re doing an apples-to-apples comparison on our benchmarks, and that all the nice things that Mono currently gets us will also work correctly under the new scripting engine.&lt;br /&gt;
&lt;br /&gt;
Additionally, for better or for worse, LSL is here to stay. People have been writing scripts in it for 20 years, and they&#039;re going to be running on the servers for years to come. Performance and memory usage issues with LSL scripts will continue to matter for the foreseeable future. The faster the server can run existing LSL scripts, the more time it can dedicate to running newer Lua scripts. Memory usage has been a particular problem with the existing Mono scripting engine, and this would help us address this.&lt;br /&gt;
&lt;br /&gt;
=== What about running scripts on the viewer-side if I want viewer-side effects? Does this have any interaction with the work adding Lua addons into the viewer? ===&lt;br /&gt;
&lt;br /&gt;
No, the work on adding Lua addons to the viewer is a separate effort. Lua scripts on SL will strictly run on the region, and not on the people&#039;s viewers. We&#039;re focused on providing a high-quality replacement to the existing server-side scripts before we look at anything else.&lt;br /&gt;
&lt;br /&gt;
Note that we are all too aware of the current difficulty of scripting HUD UIs for Second Life (we make a lot of our own) and we&#039;re thinking of ways we can make things better, but there are no definite plans, and it wouldn&#039;t involve running scripts on the viewer in the near-term. The current UI tooling exposed to Lua viewer addons isn&#039;t tooled for being called from untrusted code either way, so lack of &amp;quot;client-side&amp;quot; scripts is unlikely to hinder any effort to create a better API for HUDs for in-world systems.&lt;br /&gt;
&lt;br /&gt;
=== You said Lua runs LSL faster than Mono? It uses less memory? How? I&#039;m suspicious. ===&lt;br /&gt;
&lt;br /&gt;
A reasonable suspicion! We were also suspicious and suspected that Lua would be slightly slower, but in reality, yes, Lua can run LSL scripts quite a bit faster than the custom-patched Mono 2.6 we currently use. The best LSL on Mono has performed compared to Luau in our internal tests is 10% slower than Luau. Similarly, most LSL on Luau scripts end up being around half the size of Mono scripts. That means much more memory to play with in your LSL scripts.&lt;br /&gt;
&lt;br /&gt;
The reason why isn&#039;t intuitive, after all, Mono is a lot more intricate and has a lot of fancy performance optimizations like a JIT optimizer, but it becomes clear when you look at the bytecode that&#039;s generated for LSL on each scripting engine.&lt;br /&gt;
&lt;br /&gt;
Consider the following LSL function as an example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lsl2&amp;quot;&amp;gt;&lt;br /&gt;
float loudSubtract(float val, float sub) {&lt;br /&gt;
    // subtract, telling the owner what we did&lt;br /&gt;
	float new_val = val - sub;&lt;br /&gt;
	llOwnerSay(&amp;quot;New val is &amp;quot; + (string)new_val);&lt;br /&gt;
	return new_val;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This compiles to the following Luau bytecode (subject to further optimization):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Function 0 (_floudSubtract):&lt;br /&gt;
SUB R2 R0 R1&lt;br /&gt;
GETIMPORT R3 2 [ll.OwnerSay]&lt;br /&gt;
GETIMPORT R6 5 [lsl.cast]&lt;br /&gt;
MOVE R7 R2&lt;br /&gt;
LOADN R8 3&lt;br /&gt;
CALL R6 2 1&lt;br /&gt;
LOADK R5 K6 [&#039;New val is &#039;]&lt;br /&gt;
CONCAT R4 R5 R6&lt;br /&gt;
CALL R3 1 0&lt;br /&gt;
RETURN R2 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Okay, that&#039;s pretty decent. Some places where things could be more optimized, but no big deal. The equivalent Mono bytecode is (and bear with us, this is long:)&lt;br /&gt;
&lt;br /&gt;
{| role=&amp;quot;presentation&amp;quot; class=&amp;quot;wikitable mw-collapsible autocollapse mw-collapsed&amp;quot;&lt;br /&gt;
| &amp;lt;strong&amp;gt;Mono bytecode&amp;lt;/strong&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre&amp;gt;&lt;br /&gt;
 .class public auto ansi serializable beforefieldinit&lt;br /&gt;
  LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    extends [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 &#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 val&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; l1&#039;&lt;br /&gt;
&lt;br /&gt;
  .method compilercontrolled specialname rtspecialname instance void&lt;br /&gt;
    .ctor(&lt;br /&gt;
      int32 _param1,&lt;br /&gt;
      class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 _param2,&lt;br /&gt;
      float32 _param3,&lt;br /&gt;
      float32 _param4,&lt;br /&gt;
      float32 _param5,&lt;br /&gt;
      int32 _param6&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 4&lt;br /&gt;
&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: call         instance void [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame::.ctor()&lt;br /&gt;
&lt;br /&gt;
    // [29 5 - 29 28]&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldarg.s      _param1&lt;br /&gt;
    IL_0009: nop&lt;br /&gt;
    IL_000a: nop&lt;br /&gt;
    IL_000b: nop&lt;br /&gt;
    IL_000c: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
    // [30 5 - 30 34]&lt;br /&gt;
    IL_0011: ldarg.0      // this&lt;br /&gt;
    IL_0012: ldarg.s      _param2&lt;br /&gt;
    IL_0014: nop&lt;br /&gt;
    IL_0015: nop&lt;br /&gt;
    IL_0016: nop&lt;br /&gt;
    IL_0017: stfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 23]&lt;br /&gt;
    IL_001c: ldarg.0      // this&lt;br /&gt;
    IL_001d: ldarg.s      _param3&lt;br /&gt;
    IL_001f: nop&lt;br /&gt;
    IL_0020: nop&lt;br /&gt;
    IL_0021: nop&lt;br /&gt;
    IL_0022: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
&lt;br /&gt;
    // [32 5 - 32 23]&lt;br /&gt;
    IL_0027: ldarg.0      // this&lt;br /&gt;
    IL_0028: ldarg.s      _param4&lt;br /&gt;
    IL_002a: nop&lt;br /&gt;
    IL_002b: nop&lt;br /&gt;
    IL_002c: nop&lt;br /&gt;
    IL_002d: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
    // [33 5 - 33 28]&lt;br /&gt;
    IL_0032: ldarg.0      // this&lt;br /&gt;
    IL_0033: ldarg.s      _param5&lt;br /&gt;
    IL_0035: nop&lt;br /&gt;
    IL_0036: nop&lt;br /&gt;
    IL_0037: nop&lt;br /&gt;
    IL_0038: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
    // [34 5 - 34 28]&lt;br /&gt;
    IL_003d: ldarg.0      // this&lt;br /&gt;
    IL_003e: ldarg.s      _param6&lt;br /&gt;
    IL_0040: nop&lt;br /&gt;
    IL_0041: nop&lt;br /&gt;
    IL_0042: nop&lt;br /&gt;
    IL_0043: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0048: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor&lt;br /&gt;
&lt;br /&gt;
  .method public virtual instance object&lt;br /&gt;
    Resume() cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 8&lt;br /&gt;
&lt;br /&gt;
    // [37 37 - 37 99]&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: ldfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
    IL_000c: ldarg.0      // this&lt;br /&gt;
    IL_000d: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
    IL_0012: call         instance float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575::gloudSubtract(float32, float32)&lt;br /&gt;
    IL_0017: box          [mscorlib]System.Single&lt;br /&gt;
    IL_001c: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::Resume&lt;br /&gt;
} // end of class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 // ... Within the actual script class&lt;br /&gt;
&lt;br /&gt;
 .method public hidebysig instance float32&lt;br /&gt;
    gloudSubtract(&lt;br /&gt;
      float32 val,&lt;br /&gt;
      float32 &#039;sub&#039;&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 16&lt;br /&gt;
    .locals init (&lt;br /&gt;
      [0] float32 num1,&lt;br /&gt;
      [1] int32 &#039;l1[34-35], num2[69-151], num2[151-152]&#039;,&lt;br /&gt;
      [2] class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame c9eec9e37575gloudSubtractFrame,&lt;br /&gt;
      [3] float32 num3&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    // [17 5 - 17 60]&lt;br /&gt;
    IL_0000: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsRestoring()&lt;br /&gt;
    IL_0005: brfalse      IL_0036&lt;br /&gt;
&lt;br /&gt;
    // [19 7 - 19 202]&lt;br /&gt;
    IL_000a: call         class [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame [UThread]LindenLab.SecondLife.UThread.UThread::Pop()&lt;br /&gt;
    IL_000f: castclass    LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0014: stloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
    // [20 7 - 20 53]&lt;br /&gt;
    IL_0015: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0016: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
    IL_001b: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [21 7 - 21 55]&lt;br /&gt;
    IL_001c: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_001d: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0022: stloc.1      // l1&lt;br /&gt;
&lt;br /&gt;
    // [22 7 - 22 55]&lt;br /&gt;
    IL_0023: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0024: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
    IL_0029: switch       (IL_0085, IL_004b)&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 58]&lt;br /&gt;
    IL_0036: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_003b: brfalse      IL_004b&lt;br /&gt;
&lt;br /&gt;
    // [33 7 - 33 15]&lt;br /&gt;
    IL_0040: ldc.i4       1 // 0x00000001&lt;br /&gt;
    IL_0045: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [34 7 - 34 20]&lt;br /&gt;
    IL_0046: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [37 5 - 37 70]&lt;br /&gt;
    IL_004b: ldarg.s      &#039;sub&#039;&lt;br /&gt;
    IL_004d: nop&lt;br /&gt;
    IL_004e: nop&lt;br /&gt;
    IL_004f: nop&lt;br /&gt;
    IL_0050: ldarg.s      val&lt;br /&gt;
    IL_0052: nop&lt;br /&gt;
    IL_0053: nop&lt;br /&gt;
    IL_0054: nop&lt;br /&gt;
    IL_0055: call         float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&lt;br /&gt;
    IL_005a: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [38 5 - 38 84]&lt;br /&gt;
    IL_005b: ldloc.0      // num1&lt;br /&gt;
    IL_005c: call         string [LslLibrary]LindenLab.SecondLife.LslRunTime::ToString(float32)&lt;br /&gt;
    IL_0061: ldstr        &amp;quot;New val is &amp;quot;&lt;br /&gt;
    IL_0066: call         string [LslUserScript]LindenLab.SecondLife.LslUserScript::Add(string, string)&lt;br /&gt;
    IL_006b: call         void [LslLibrary]LindenLab.SecondLife.Library::llOwnerSay(string)&lt;br /&gt;
&lt;br /&gt;
    // [39 5 - 39 58]&lt;br /&gt;
    IL_0070: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_0075: brfalse      IL_0085&lt;br /&gt;
&lt;br /&gt;
    // [41 7 - 41 15]&lt;br /&gt;
    IL_007a: ldc.i4       0 // 0x00000000&lt;br /&gt;
    IL_007f: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [42 7 - 42 20]&lt;br /&gt;
    IL_0080: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [45 5 - 45 17]&lt;br /&gt;
    IL_0085: ldloc.0      // num1&lt;br /&gt;
    IL_0086: ret&lt;br /&gt;
    IL_0087: nop&lt;br /&gt;
&lt;br /&gt;
    // [47 5 - 47 194]&lt;br /&gt;
    IL_0088: ldloc.1      // num2&lt;br /&gt;
    IL_0089: ldarg.0      // this&lt;br /&gt;
    IL_008a: ldarg        val&lt;br /&gt;
    IL_008e: nop&lt;br /&gt;
    IL_008f: nop&lt;br /&gt;
    IL_0090: ldarg        &#039;sub&#039;&lt;br /&gt;
    IL_0094: nop&lt;br /&gt;
    IL_0095: nop&lt;br /&gt;
    IL_0096: ldloc.0      // num1&lt;br /&gt;
    IL_0097: ldloc.1      // num2&lt;br /&gt;
    IL_0098: newobj       instance void LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor(int32, class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575, float32, float32, float32, int32)&lt;br /&gt;
    IL_009d: pop&lt;br /&gt;
&lt;br /&gt;
    // [49 5 - 49 17]&lt;br /&gt;
    IL_009e: ldloc.3      // num3&lt;br /&gt;
    IL_009f: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
While this might look unreasonably large, this is also pretty similar to how &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt; C# functions that have &amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt;s get compiled. The code required to allow a function to pause in the middle to let other things run is necessarily very large, and our Mono bytecode instrumentation is essentially a special variant of that which optimizes for cases where the function won&#039;t have to pause. Try pasting the following code into sharplab.io (while decompiling to IL) and you&#039;ll see something very similar:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
using System.Threading.Tasks;&lt;br /&gt;
public class Example {&lt;br /&gt;
    public async Task llOwnerSay(string val)&lt;br /&gt;
    {&lt;br /&gt;
        // stub&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    public async Task&amp;lt;float&amp;gt; loudSubtract(float val, float sub) {&lt;br /&gt;
        float new_val = val - sub;&lt;br /&gt;
        await llOwnerSay(&amp;quot;New val is &amp;quot; + new_val.ToString());&lt;br /&gt;
        return new_val;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Lua bytecode ends up being a lot smaller, because the Lua scripting engine itself knows how to handle this case, and doesn&#039;t need to add any special code to your scripts to allow them to be paused and resumed in the middle of a function.&lt;br /&gt;
&lt;br /&gt;
Even though Mono has a JIT optimizer, which should in theory make the script run faster, this sort of code is very unusual, and the JIT optimizer isn&#039;t able to optimize it very well. That means that even though Luau itself is in theory slower, it&#039;s able to run LSL much faster than Mono can. &lt;br /&gt;
&lt;br /&gt;
You might also notice things like call &amp;lt;code&amp;gt;float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&amp;lt;/code&amp;gt; in the Mono code. Those are necessary because of LSL&#039;s weird operand evaluation order, but are unnecessary in Luau because of the more efficient form of bytecode that Luau uses.&lt;br /&gt;
&lt;br /&gt;
In general, C# doesn&#039;t aim at producing very small bytecode for async functions, which is important for something like Second Life where you might have thousands of different individual scripts running within a region. Luau does generate small bytecode, while still having decent performance, making it a good choice for Second Life. And hey, strings are UTF-8 in Lua instead of UTF-16 as in Mono, so they use much less memory for most data as well.&lt;br /&gt;
&lt;br /&gt;
=== Are we going to get a higher memory limit in scripts? ===&lt;br /&gt;
&lt;br /&gt;
Not at first, Luau uses less memory than Mono, so you shouldn&#039;t really miss it. You&#039;re not going to start hitting script memory limits just because you converted your script to Lua. Using Lua proper should allow you to more precisely control memory usage in your scripts, e.g. by being able to handle memory allocation errors in &amp;lt;code&amp;gt;pcall()&amp;lt;/code&amp;gt; and access to the mutable &amp;lt;code&amp;gt;buffer&amp;lt;/code&amp;gt; class for byte-packing.&lt;br /&gt;
&lt;br /&gt;
There is some interest in giving specific scripts a little more memory if they&#039;re designated &amp;quot;high priority&amp;quot;, like a main &amp;quot;game manager&amp;quot; object for an experience, but there are no specific plans for this at the moment.&lt;br /&gt;
&lt;br /&gt;
=== Are my existing LSL scripts going to run on Lua Automatically? ===&lt;br /&gt;
&lt;br /&gt;
While we can run LSL in Lua now, and the idea is to eventually phase out Mono, there’s no immediate plans to transparently migrate existing scripts over to Lua. There are ideas about how this could be done safely without breaking existing content, but we want to make sure we have everything right before we start work on this.&lt;br /&gt;
&lt;br /&gt;
=== How does interoperation with LSL work? ===&lt;br /&gt;
&lt;br /&gt;
Everything that you expect from LSL should have some equivalent in Lua. There’s &amp;lt;code&amp;gt;key&amp;lt;/code&amp;gt;s, &amp;lt;code&amp;gt;rotation&amp;lt;/code&amp;gt;s and &amp;lt;code&amp;gt;vector&amp;lt;/code&amp;gt;s and something like &amp;lt;code&amp;gt;some_vec * some_rot&amp;lt;/code&amp;gt; in LSL is the exact same in Lua scripts.&lt;br /&gt;
&lt;br /&gt;
All LSL functions will be available in Lua, and something like &amp;lt;code&amp;gt;llSay(0, “hi!”)&amp;lt;/code&amp;gt; is just &amp;lt;code&amp;gt;ll.Say(0, “hi!”)&amp;lt;/code&amp;gt; now. What about &amp;lt;code&amp;gt;llSetPos(llGetPos() + &amp;lt;0, 0, 1&amp;gt;);&amp;lt;/code&amp;gt;? Just &amp;lt;code&amp;gt;ll.SetPos(ll.GetPos() + vector(0, 0, 1))&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
No special magic required here, and you can write whatever fancy wrappers you want around the existing LSL functions. We’ll be looking at how residents actually write these wrappers to inform the design of “official” higher-level Lua APIs, so we’re excited to see your ideas!&lt;br /&gt;
&lt;br /&gt;
=== Can I mix LSL and Lua in the same script? ===&lt;br /&gt;
&lt;br /&gt;
No, although most things should be a direct translation. At some point we may make a tool to help you do this, but there are no specific plans at the moment.&lt;br /&gt;
&lt;br /&gt;
If your object has multiple scripts, you can mix LSL and Lua within the same object, though.&lt;br /&gt;
&lt;br /&gt;
=== Can we use all of Lua? Is anything not there? ===&lt;br /&gt;
&lt;br /&gt;
Most everything will be there, except for two notable exceptions:&lt;br /&gt;
&lt;br /&gt;
- &amp;lt;code&amp;gt;getfenv()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;setfenv()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;_ENV&amp;lt;/code&amp;gt; are not accessible because they necessarily make the script harder to optimize. Usually people only use this for mocking in test code, and this can be accomplished by passing in a parameter with a table of function references rather than pretending you have different global variable values for certain functions.&lt;br /&gt;
- &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; is not supported since it has a huge impact on performance. For those not familiar, this is equivalent to eval() in other languages. Every single time you call &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; the compiler has to go over what you passed in, compile it, and then load the generated bytecode. The Lua compiler isn’t particularly fast, and doing this can cause your script to halt in a way that we can’t interrupt to give other scripts time to run. This is a big part of why &amp;lt;code&amp;gt;eval()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; are generally considered poor programming practice.&lt;br /&gt;
&lt;br /&gt;
The one real use-case we could think of for &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; is a debugger, and we’re looking at implementing a “real” debugger that lets you step through your code. Note that this won’t happen with the initial release, but watch this space!&lt;br /&gt;
&lt;br /&gt;
One thing that is planned, but will likely not be in the initial release is the coroutine library. This is one we’d really like to have in there, so you can do fancy things like have multiple “timers” in a script by just having coroutines that sleep every so often, but multiple running coroutines within the same script doesn’t fit well with the existing scripting architecture the Second Life server uses. Rest assured, this will be in there soon™ after Lua is released, but in the meantime you can still use something like the &amp;lt;code&amp;gt;Promise&amp;lt;/code&amp;gt; chaining that JavaScript uses to write asynchronous event handlers.&lt;br /&gt;
&lt;br /&gt;
We’ll have code demonstrating &amp;quot;proper&amp;quot; usage of Lua around the time that the public beta starts.&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1216874</id>
		<title>Lua FAQ</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1216874"/>
		<updated>2024-07-02T20:01:01Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
=== Can I keep using LSL? ===&lt;br /&gt;
&lt;br /&gt;
Yep! LSL should work exactly the same as before, and a lot of testing work has been done (and is being done) to ensure that is the case. Even if you’re an LSL pro and rely on things like &amp;lt;code&amp;gt;(some_list != [])&amp;lt;/code&amp;gt; for getting the list length, or strange things like &amp;lt;code&amp;gt;((largestring=&amp;quot;&amp;quot;) + largestring)&amp;lt;/code&amp;gt;, you’re fully covered. Those still work, and now have optimized implementations.&lt;br /&gt;
&lt;br /&gt;
In fact, LSL will get better than ever before, running faster, and using less memory! The generated bytecode for an LSL script in Lua is generally half the size of a Mono script, and most strings take up half as much memory by virtue of using UTF-8 instead of UTF-16.&lt;br /&gt;
&lt;br /&gt;
We’ve taken a lot of care in writing the new LSL compiler to make the scripting experience for people who prefer LSL better than it was before.&lt;br /&gt;
&lt;br /&gt;
=== Are you going to stop adding functions / events to LSL? ===&lt;br /&gt;
&lt;br /&gt;
In the general case, no. A big part of the work we’ve done is making it easy to share functions between LSL and Lua without much extra effort. For events and functions that can be reasonably exposed to LSL (i.e., they only use simple types that LSL has anyway) then LSL should still get those.&lt;br /&gt;
&lt;br /&gt;
For more advanced features, the idea is that there should still be a path for exposing them to LSL. Obviously, with Lua it’s possible to have object-oriented APIs that would not be possible in LSL, but those objects will generally be written in a way that calling a method on a “special” object we introduce in Lua will really boil down to a function call that can be expressed in LSL.&lt;br /&gt;
&lt;br /&gt;
Not a real example, but &amp;lt;code&amp;gt;some_object:giveInventoryList(&amp;quot;folder_name&amp;quot;, inventory_list)&amp;lt;/code&amp;gt; might really boil down to a &amp;lt;code&amp;gt;llGiveInventoryList(some_object_id, &amp;quot;folder_name&amp;quot;, &amp;quot;inventory_list&amp;quot;);&amp;lt;/code&amp;gt; call. In that way, most additional Lua functionality will be a fancy “easier” wrapper around functions shared with LSL!&lt;br /&gt;
&lt;br /&gt;
The caveat is if functions are added that use new types that aren&#039;t in LSL (like functions that take in a &amp;lt;code&amp;gt;key -&amp;gt; value&amp;lt;/code&amp;gt; map) likely won&#039;t be exposed to LSL, as there&#039;s no way to represent them in LSL anyway.&lt;br /&gt;
&lt;br /&gt;
=== Wait, LSL can run on Lua now? How? Why would you do that? ===&lt;br /&gt;
&lt;br /&gt;
A reasonable question! It sounds crazy at first, but as it turns out, LSL maps really well to Lua bytecode, and we already have a compiler that can output both Mono and legacy LSO2 bytecode. Writing a compiler that could output Lua bytecode wasn&#039;t terribly difficult, and it allows LSL scripts to run faster, and be more memory-efficient. Most importantly, it allows us to test behavior of the new scripting engine against the behavior of the existing Mono scripting engine. This helps us make sure we&#039;re doing an apples-to-apples comparison on our benchmarks, and that all the nice things that Mono currently gets us will also work correctly under the new scripting engine.&lt;br /&gt;
&lt;br /&gt;
Additionally, for better or for worse, LSL is here to stay. People have been writing scripts in it for 20 years, and they&#039;re going to be running on the servers for years to come. Performance and memory usage issues with LSL scripts will continue to matter for the foreseeable future. The faster the server can run existing LSL scripts, the more time it can dedicate to running newer Lua scripts. Memory usage has been a particular problem with the existing Mono scripting engine, and this would help us address this.&lt;br /&gt;
&lt;br /&gt;
=== What about running scripts on the viewer-side if I want viewer-side effects? Does this have any interaction with the work adding Lua addons into the viewer? ===&lt;br /&gt;
&lt;br /&gt;
No, the work on adding Lua addons to the viewer is a separate effort. Lua scripts on SL will strictly run on the region, and not on the people&#039;s viewers. We&#039;re focused on providing a high-quality replacement to the existing server-side scripts before we look at anything else.&lt;br /&gt;
&lt;br /&gt;
Note that we are all too aware of the current difficulty of scripting HUD UIs for Second Life (we make a lot of our own) and we&#039;re thinking of ways we can make things better, but there are no definite plans, and it wouldn&#039;t involve running scripts on the viewer in the near-term. The current UI tooling exposed to Lua viewer addons isn&#039;t tooled for being called from untrusted code either way, so lack of &amp;quot;client-side&amp;quot; scripts is unlikely to hinder any effort to create a better API for HUDs for in-world systems.&lt;br /&gt;
&lt;br /&gt;
=== You said Lua runs LSL faster than Mono? It uses less memory? How? I&#039;m suspicious. ===&lt;br /&gt;
&lt;br /&gt;
A reasonable suspicion! We were also suspicious and suspected that Lua would be slightly slower, but in reality, yes, Lua can run LSL scripts quite a bit faster than the custom-patched Mono 2.6 we currently use. The best LSL on Mono has performed compared to Luau in our internal tests is 10% slower than Luau. Similarly, most LSL on Luau scripts end up being around half the size of Mono scripts. That means much more memory to play with in your LSL scripts.&lt;br /&gt;
&lt;br /&gt;
The reason why isn&#039;t intuitive, after all, Mono is a lot more intricate and has a lot of fancy performance optimizations like a JIT optimizer, but it becomes clear when you look at the bytecode that&#039;s generated for LSL on each scripting engine.&lt;br /&gt;
&lt;br /&gt;
Consider the following LSL function as an example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lsl2&amp;quot;&amp;gt;&lt;br /&gt;
float loudSubtract(float val, float sub) {&lt;br /&gt;
    // subtract, telling the owner what we did&lt;br /&gt;
	float new_val = val - sub;&lt;br /&gt;
	llOwnerSay(&amp;quot;New val is &amp;quot; + (string)new_val);&lt;br /&gt;
	return new_val;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This compiles to the following Luau bytecode (subject to further optimization):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Function 0 (_floudSubtract):&lt;br /&gt;
SUB R2 R0 R1&lt;br /&gt;
GETIMPORT R3 2 [ll.OwnerSay]&lt;br /&gt;
GETIMPORT R6 5 [lsl.cast]&lt;br /&gt;
MOVE R7 R2&lt;br /&gt;
LOADN R8 3&lt;br /&gt;
CALL R6 2 1&lt;br /&gt;
LOADK R5 K6 [&#039;New val is &#039;]&lt;br /&gt;
CONCAT R4 R5 R6&lt;br /&gt;
CALL R3 1 0&lt;br /&gt;
RETURN R2 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Okay, that&#039;s pretty decent. Some places where things could be more optimized, but no big deal. The equivalent Mono bytecode is (and bear with us, this is long:)&lt;br /&gt;
&lt;br /&gt;
{| role=&amp;quot;presentation&amp;quot; class=&amp;quot;wikitable mw-collapsible autocollapse mw-collapsed&amp;quot;&lt;br /&gt;
| &amp;lt;strong&amp;gt;Mono bytecode&amp;lt;/strong&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre&amp;gt;&lt;br /&gt;
 .class public auto ansi serializable beforefieldinit&lt;br /&gt;
  LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    extends [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 &#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 val&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; l1&#039;&lt;br /&gt;
&lt;br /&gt;
  .method compilercontrolled specialname rtspecialname instance void&lt;br /&gt;
    .ctor(&lt;br /&gt;
      int32 _param1,&lt;br /&gt;
      class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 _param2,&lt;br /&gt;
      float32 _param3,&lt;br /&gt;
      float32 _param4,&lt;br /&gt;
      float32 _param5,&lt;br /&gt;
      int32 _param6&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 4&lt;br /&gt;
&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: call         instance void [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame::.ctor()&lt;br /&gt;
&lt;br /&gt;
    // [29 5 - 29 28]&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldarg.s      _param1&lt;br /&gt;
    IL_0009: nop&lt;br /&gt;
    IL_000a: nop&lt;br /&gt;
    IL_000b: nop&lt;br /&gt;
    IL_000c: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
    // [30 5 - 30 34]&lt;br /&gt;
    IL_0011: ldarg.0      // this&lt;br /&gt;
    IL_0012: ldarg.s      _param2&lt;br /&gt;
    IL_0014: nop&lt;br /&gt;
    IL_0015: nop&lt;br /&gt;
    IL_0016: nop&lt;br /&gt;
    IL_0017: stfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 23]&lt;br /&gt;
    IL_001c: ldarg.0      // this&lt;br /&gt;
    IL_001d: ldarg.s      _param3&lt;br /&gt;
    IL_001f: nop&lt;br /&gt;
    IL_0020: nop&lt;br /&gt;
    IL_0021: nop&lt;br /&gt;
    IL_0022: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
&lt;br /&gt;
    // [32 5 - 32 23]&lt;br /&gt;
    IL_0027: ldarg.0      // this&lt;br /&gt;
    IL_0028: ldarg.s      _param4&lt;br /&gt;
    IL_002a: nop&lt;br /&gt;
    IL_002b: nop&lt;br /&gt;
    IL_002c: nop&lt;br /&gt;
    IL_002d: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
    // [33 5 - 33 28]&lt;br /&gt;
    IL_0032: ldarg.0      // this&lt;br /&gt;
    IL_0033: ldarg.s      _param5&lt;br /&gt;
    IL_0035: nop&lt;br /&gt;
    IL_0036: nop&lt;br /&gt;
    IL_0037: nop&lt;br /&gt;
    IL_0038: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
    // [34 5 - 34 28]&lt;br /&gt;
    IL_003d: ldarg.0      // this&lt;br /&gt;
    IL_003e: ldarg.s      _param6&lt;br /&gt;
    IL_0040: nop&lt;br /&gt;
    IL_0041: nop&lt;br /&gt;
    IL_0042: nop&lt;br /&gt;
    IL_0043: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0048: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor&lt;br /&gt;
&lt;br /&gt;
  .method public virtual instance object&lt;br /&gt;
    Resume() cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 8&lt;br /&gt;
&lt;br /&gt;
    // [37 37 - 37 99]&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: ldfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
    IL_000c: ldarg.0      // this&lt;br /&gt;
    IL_000d: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
    IL_0012: call         instance float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575::gloudSubtract(float32, float32)&lt;br /&gt;
    IL_0017: box          [mscorlib]System.Single&lt;br /&gt;
    IL_001c: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::Resume&lt;br /&gt;
} // end of class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 // ... Within the actual script class&lt;br /&gt;
&lt;br /&gt;
 .method public hidebysig instance float32&lt;br /&gt;
    gloudSubtract(&lt;br /&gt;
      float32 val,&lt;br /&gt;
      float32 &#039;sub&#039;&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 16&lt;br /&gt;
    .locals init (&lt;br /&gt;
      [0] float32 num1,&lt;br /&gt;
      [1] int32 &#039;l1[34-35], num2[69-151], num2[151-152]&#039;,&lt;br /&gt;
      [2] class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame c9eec9e37575gloudSubtractFrame,&lt;br /&gt;
      [3] float32 num3&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    // [17 5 - 17 60]&lt;br /&gt;
    IL_0000: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsRestoring()&lt;br /&gt;
    IL_0005: brfalse      IL_0036&lt;br /&gt;
&lt;br /&gt;
    // [19 7 - 19 202]&lt;br /&gt;
    IL_000a: call         class [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame [UThread]LindenLab.SecondLife.UThread.UThread::Pop()&lt;br /&gt;
    IL_000f: castclass    LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0014: stloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
    // [20 7 - 20 53]&lt;br /&gt;
    IL_0015: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0016: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
    IL_001b: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [21 7 - 21 55]&lt;br /&gt;
    IL_001c: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_001d: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0022: stloc.1      // l1&lt;br /&gt;
&lt;br /&gt;
    // [22 7 - 22 55]&lt;br /&gt;
    IL_0023: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0024: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
    IL_0029: switch       (IL_0085, IL_004b)&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 58]&lt;br /&gt;
    IL_0036: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_003b: brfalse      IL_004b&lt;br /&gt;
&lt;br /&gt;
    // [33 7 - 33 15]&lt;br /&gt;
    IL_0040: ldc.i4       1 // 0x00000001&lt;br /&gt;
    IL_0045: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [34 7 - 34 20]&lt;br /&gt;
    IL_0046: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [37 5 - 37 70]&lt;br /&gt;
    IL_004b: ldarg.s      &#039;sub&#039;&lt;br /&gt;
    IL_004d: nop&lt;br /&gt;
    IL_004e: nop&lt;br /&gt;
    IL_004f: nop&lt;br /&gt;
    IL_0050: ldarg.s      val&lt;br /&gt;
    IL_0052: nop&lt;br /&gt;
    IL_0053: nop&lt;br /&gt;
    IL_0054: nop&lt;br /&gt;
    IL_0055: call         float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&lt;br /&gt;
    IL_005a: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [38 5 - 38 84]&lt;br /&gt;
    IL_005b: ldloc.0      // num1&lt;br /&gt;
    IL_005c: call         string [LslLibrary]LindenLab.SecondLife.LslRunTime::ToString(float32)&lt;br /&gt;
    IL_0061: ldstr        &amp;quot;New val is &amp;quot;&lt;br /&gt;
    IL_0066: call         string [LslUserScript]LindenLab.SecondLife.LslUserScript::Add(string, string)&lt;br /&gt;
    IL_006b: call         void [LslLibrary]LindenLab.SecondLife.Library::llOwnerSay(string)&lt;br /&gt;
&lt;br /&gt;
    // [39 5 - 39 58]&lt;br /&gt;
    IL_0070: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_0075: brfalse      IL_0085&lt;br /&gt;
&lt;br /&gt;
    // [41 7 - 41 15]&lt;br /&gt;
    IL_007a: ldc.i4       0 // 0x00000000&lt;br /&gt;
    IL_007f: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [42 7 - 42 20]&lt;br /&gt;
    IL_0080: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [45 5 - 45 17]&lt;br /&gt;
    IL_0085: ldloc.0      // num1&lt;br /&gt;
    IL_0086: ret&lt;br /&gt;
    IL_0087: nop&lt;br /&gt;
&lt;br /&gt;
    // [47 5 - 47 194]&lt;br /&gt;
    IL_0088: ldloc.1      // num2&lt;br /&gt;
    IL_0089: ldarg.0      // this&lt;br /&gt;
    IL_008a: ldarg        val&lt;br /&gt;
    IL_008e: nop&lt;br /&gt;
    IL_008f: nop&lt;br /&gt;
    IL_0090: ldarg        &#039;sub&#039;&lt;br /&gt;
    IL_0094: nop&lt;br /&gt;
    IL_0095: nop&lt;br /&gt;
    IL_0096: ldloc.0      // num1&lt;br /&gt;
    IL_0097: ldloc.1      // num2&lt;br /&gt;
    IL_0098: newobj       instance void LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor(int32, class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575, float32, float32, float32, int32)&lt;br /&gt;
    IL_009d: pop&lt;br /&gt;
&lt;br /&gt;
    // [49 5 - 49 17]&lt;br /&gt;
    IL_009e: ldloc.3      // num3&lt;br /&gt;
    IL_009f: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
While this might look unreasonably large, this is also pretty similar to how &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt; C# functions that have &amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt;s get compiled. The code required to allow a function to pause in the middle to let other things run is necessarily very large, and our Mono bytecode instrumentation is essentially a special variant of that which optimizes for cases where the function won&#039;t have to pause. Try pasting the following code into sharplab.io (while decompiling to IL) and you&#039;ll see something very similar:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
using System.Threading.Tasks;&lt;br /&gt;
public class Example {&lt;br /&gt;
    public async Task llOwnerSay(string val)&lt;br /&gt;
    {&lt;br /&gt;
        // stub&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    public async Task&amp;lt;float&amp;gt; loudSubtract(float val, float sub) {&lt;br /&gt;
        float new_val = val - sub;&lt;br /&gt;
        await llOwnerSay(&amp;quot;New val is &amp;quot; + new_val.ToString());&lt;br /&gt;
        return new_val;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Lua bytecode ends up being a lot smaller, because the Lua scripting engine itself knows how to handle this case, and doesn&#039;t need to add any special code to your scripts to allow them to be paused and resumed in the middle of a function.&lt;br /&gt;
&lt;br /&gt;
Even though Mono has a JIT optimizer, which should in theory make the script run faster, this sort of code is very unusual, and the JIT optimizer isn&#039;t able to optimize it very well. That means that even though Luau itself is in theory slower, it&#039;s able to run LSL much faster than Mono can. &lt;br /&gt;
&lt;br /&gt;
You might also notice things like call &amp;lt;code&amp;gt;float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&amp;lt;/code&amp;gt; in the Mono code. Those are necessary because of LSL&#039;s weird operand evaluation order, but are unnecessary in Luau because of the more efficient form of bytecode that Luau uses.&lt;br /&gt;
&lt;br /&gt;
In general, C# doesn&#039;t aim at producing very small bytecode for async functions, which is important for something like Second Life where you might have thousands of different individual scripts running within a region. Luau does generate small bytecode, while still having decent performance, making it a good choice for Second Life. And hey, strings are UTF-8 in Lua instead of UTF-16 as in Mono, so they use much less memory for most data as well.&lt;br /&gt;
&lt;br /&gt;
=== Are we going to get a higher memory limit in scripts? ===&lt;br /&gt;
&lt;br /&gt;
Not at first, Luau uses less memory than Mono, so you shouldn&#039;t really miss it. You&#039;re not going to start hitting script memory limits just because you converted your script to Lua. Using Lua proper should allow you to more precisely control memory usage in your scripts, e.g. by being able to handle memory allocation errors in &amp;lt;code&amp;gt;pcall()&amp;lt;/code&amp;gt; and access to the mutable &amp;lt;code&amp;gt;buffer&amp;lt;/code&amp;gt; class for byte-packing.&lt;br /&gt;
&lt;br /&gt;
There is some interest in giving specific scripts a little more memory if they&#039;re designated &amp;quot;high priority&amp;quot;, like a main &amp;quot;game manager&amp;quot; object for an experience, but there are no specific plans for this at the moment.&lt;br /&gt;
&lt;br /&gt;
=== Are my existing LSL scripts going to run on Lua Automatically? ===&lt;br /&gt;
&lt;br /&gt;
While we can run LSL in Lua now, and the idea is to eventually phase out Mono, there’s no immediate plans to transparently migrate existing scripts over to Lua. There are ideas about how this could be done safely without breaking existing content, but we want to make sure we have everything right before we start work on this.&lt;br /&gt;
&lt;br /&gt;
=== How does interoperation with LSL work? ===&lt;br /&gt;
&lt;br /&gt;
Everything that you expect from LSL should have some equivalent in Lua. There’s &amp;lt;code&amp;gt;key&amp;lt;code&amp;gt;s, &amp;lt;code&amp;gt;rotation&amp;lt;/code&amp;gt;s and &amp;lt;code&amp;gt;vector&amp;lt;/code&amp;gt;s and something like &amp;lt;code&amp;gt;some_vec * some_rot&amp;lt;/code&amp;gt; in LSL is the exact same in Lua scripts.&lt;br /&gt;
&lt;br /&gt;
All LSL functions will be available in Lua, and something like &amp;lt;code&amp;gt;llSay(0, “hi!”)&amp;lt;/code&amp;gt; is just &amp;lt;code&amp;gt;ll.Say(0, “hi!”)&amp;lt;/code&amp;gt; now. What about &amp;lt;code&amp;gt;llSetPos(llGetPos() + &amp;lt;0, 0, 1&amp;gt;);&amp;lt;/code&amp;gt;? Just &amp;lt;code&amp;gt;ll.SetPos(ll.GetPos() + vector(0, 0, 1))&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
No special magic required here, and you can write whatever fancy wrappers you want around the existing LSL functions. We’ll be looking at how residents actually write these wrappers to inform the design of “official” higher-level Lua APIs, so we’re excited to see your ideas!&lt;br /&gt;
&lt;br /&gt;
=== Can I mix LSL and Lua in the same script? ===&lt;br /&gt;
&lt;br /&gt;
No, although most things should be a direct translation. At some point we may make a tool to help you do this, but there are no specific plans at the moment.&lt;br /&gt;
&lt;br /&gt;
If your object has multiple scripts, you can mix LSL and Lua within the same object, though.&lt;br /&gt;
&lt;br /&gt;
=== Can we use all of Lua? Is anything not there? ===&lt;br /&gt;
&lt;br /&gt;
Most everything will be there, except for two notable exceptions:&lt;br /&gt;
&lt;br /&gt;
- &amp;lt;code&amp;gt;getfenv()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;setfenv()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;_ENV&amp;lt;/code&amp;gt; are not accessible because they necessarily make the script harder to optimize. Usually people only use this for mocking in test code, and this can be accomplished by passing in a parameter with a table of function references rather than pretending you have different global variable values for certain functions.&lt;br /&gt;
- &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; is not supported since it has a huge impact on performance. For those not familiar, this is equivalent to eval() in other languages. Every single time you call &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; the compiler has to go over what you passed in, compile it, and then load the generated bytecode. The Lua compiler isn’t particularly fast, and doing this can cause your script to halt in a way that we can’t interrupt to give other scripts time to run. This is a big part of why &amp;lt;code&amp;gt;eval()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; are generally considered poor programming practice.&lt;br /&gt;
&lt;br /&gt;
The one real use-case we could think of for &amp;lt;code&amp;gt;loadstring()&amp;lt;/code&amp;gt; is a debugger, and we’re looking at implementing a “real” debugger that lets you step through your code. Note that this won’t happen with the initial release, but watch this space!&lt;br /&gt;
&lt;br /&gt;
One thing that is planned, but will likely not be in the initial release is the coroutine library. This is one we’d really like to have in there, so you can do fancy things like have multiple “timers” in a script by just having coroutines that sleep every so often, but multiple running coroutines within the same script doesn’t fit well with the existing scripting architecture the Second Life server uses. Rest assured, this will be in there soon™ after Lua is released, but in the meantime you can still use something like the &amp;lt;code&amp;gt;Promise&amp;lt;/code&amp;gt; chaining that JavaScript uses to write asynchronous event handlers.&lt;br /&gt;
&lt;br /&gt;
We’ll have code demonstrating &amp;quot;proper&amp;quot; usage of Lua around the time that the public beta starts.&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1216873</id>
		<title>Lua FAQ</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=Lua_FAQ&amp;diff=1216873"/>
		<updated>2024-07-02T19:56:12Z</updated>

		<summary type="html">&lt;p&gt;Signal Linden: Created page with &amp;quot;__TOC__  === Can I keep using LSL? ===  Yep! LSL should work exactly the same as before, and a lot of testing work has been done (and is being done) to ensure that is the case...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
=== Can I keep using LSL? ===&lt;br /&gt;
&lt;br /&gt;
Yep! LSL should work exactly the same as before, and a lot of testing work has been done (and is being done) to ensure that is the case. Even if you’re an LSL pro and rely on things like &amp;lt;code&amp;gt;(some_list != [])&amp;lt;/code&amp;gt; for getting the list length, or strange things like &amp;lt;code&amp;gt;((largestring=&amp;quot;&amp;quot;) + largestring)&amp;lt;/code&amp;gt;, you’re fully covered. Those still work, and now have optimized implementations.&lt;br /&gt;
&lt;br /&gt;
In fact, LSL will get better than ever before, running faster, and using less memory! The generated bytecode for an LSL script in Lua is generally half the size of a Mono script, and most strings take up half as much memory by virtue of using UTF-8 instead of UTF-16.&lt;br /&gt;
&lt;br /&gt;
We’ve taken a lot of care in writing the new LSL compiler to make the scripting experience for people who prefer LSL better than it was before.&lt;br /&gt;
&lt;br /&gt;
=== Are you going to stop adding functions / events to LSL? ===&lt;br /&gt;
&lt;br /&gt;
In the general case, no. A big part of the work we’ve done is making it easy to share functions between LSL and Lua without much extra effort. For events and functions that can be reasonably exposed to LSL (i.e., they only use simple types that LSL has anyway) then LSL should still get those.&lt;br /&gt;
&lt;br /&gt;
For more advanced features, the idea is that there should still be a path for exposing them to LSL. Obviously, with Lua it’s possible to have object-oriented APIs that would not be possible in LSL, but those objects will generally be written in a way that calling a method on a “special” object we introduce in Lua will really boil down to a function call that can be expressed in LSL.&lt;br /&gt;
&lt;br /&gt;
Not a real example, but &amp;lt;code&amp;gt;some_object:giveInventoryList(&amp;quot;folder_name&amp;quot;, inventory_list)&amp;lt;/code&amp;gt; might really boil down to a &amp;lt;code&amp;gt;llGiveInventoryList(some_object_id, &amp;quot;folder_name&amp;quot;, &amp;quot;inventory_list&amp;quot;);&amp;lt;/code&amp;gt; call. In that way, most additional Lua functionality will be a fancy “easier” wrapper around functions shared with LSL!&lt;br /&gt;
&lt;br /&gt;
The caveat is if functions are added that use new types that aren&#039;t in LSL (like functions that take in a &amp;lt;code&amp;gt;key -&amp;gt; value&amp;lt;/code&amp;gt; map) likely won&#039;t be exposed to LSL, as there&#039;s no way to represent them in LSL anyway.&lt;br /&gt;
&lt;br /&gt;
=== Wait, LSL can run on Lua now? How? Why would you do that? ===&lt;br /&gt;
&lt;br /&gt;
A reasonable question! It sounds crazy at first, but as it turns out, LSL maps really well to Lua bytecode, and we already have a compiler that can output both Mono and legacy LSO2 bytecode. Writing a compiler that could output Lua bytecode wasn&#039;t terribly difficult, and it allows LSL scripts to run faster, and be more memory-efficient. Most importantly, it allows us to test behavior of the new scripting engine against the behavior of the existing Mono scripting engine. This helps us make sure we&#039;re doing an apples-to-apples comparison on our benchmarks, and that all the nice things that Mono currently gets us will also work correctly under the new scripting engine.&lt;br /&gt;
&lt;br /&gt;
Additionally, for better or for worse, LSL is here to stay. People have been writing scripts in it for 20 years, and they&#039;re going to be running on the servers for years to come. Performance and memory usage issues with LSL scripts will continue to matter for the foreseeable future. The faster the server can run existing LSL scripts, the more time it can dedicate to running newer Lua scripts. Memory usage has been a particular problem with the existing Mono scripting engine, and this would help us address this.&lt;br /&gt;
&lt;br /&gt;
=== What about running scripts on the viewer-side if I want viewer-side effects? Does this have any interaction with the work adding Lua addons into the viewer? ===&lt;br /&gt;
&lt;br /&gt;
No, the work on adding Lua addons to the viewer is a separate effort. Lua scripts on SL will strictly run on the region, and not on the people&#039;s viewers. We&#039;re focused on providing a high-quality replacement to the existing server-side scripts before we look at anything else.&lt;br /&gt;
&lt;br /&gt;
Note that we are all too aware of the current difficulty of scripting HUD UIs for Second Life (we make a lot of our own) and we&#039;re thinking of ways we can make things better, but there are no definite plans, and it wouldn&#039;t involve running scripts on the viewer in the near-term. The current UI tooling exposed to Lua viewer addons isn&#039;t tooled for being called from untrusted code either way, so lack of &amp;quot;client-side&amp;quot; scripts is unlikely to hinder any effort to create a better API for HUDs for in-world systems.&lt;br /&gt;
&lt;br /&gt;
=== You said Lua runs LSL faster than Mono? It uses less memory? How? I&#039;m suspicious. ===&lt;br /&gt;
&lt;br /&gt;
A reasonable suspicion! We were also suspicious and suspected that Lua would be slightly slower, but in reality, yes, Lua can run LSL scripts quite a bit faster than the custom-patched Mono 2.6 we currently use. The best LSL on Mono has performed compared to Luau in our internal tests is 10% slower than Luau. Similarly, most LSL on Luau scripts end up being around half the size of Mono scripts. That means much more memory to play with in your LSL scripts.&lt;br /&gt;
&lt;br /&gt;
The reason why isn&#039;t intuitive, after all, Mono is a lot more intricate and has a lot of fancy performance optimizations like a JIT optimizer, but it becomes clear when you look at the bytecode that&#039;s generated for LSL on each scripting engine.&lt;br /&gt;
&lt;br /&gt;
Consider the following LSL function as an example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lsl2&amp;quot;&amp;gt;&lt;br /&gt;
float loudSubtract(float val, float sub) {&lt;br /&gt;
    // subtract, telling the owner what we did&lt;br /&gt;
	float new_val = val - sub;&lt;br /&gt;
	llOwnerSay(&amp;quot;New val is &amp;quot; + (string)new_val);&lt;br /&gt;
	return new_val;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This compiles to the following Luau bytecode (subject to further optimization):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Function 0 (_floudSubtract):&lt;br /&gt;
SUB R2 R0 R1&lt;br /&gt;
GETIMPORT R3 2 [ll.OwnerSay]&lt;br /&gt;
GETIMPORT R6 5 [lsl.cast]&lt;br /&gt;
MOVE R7 R2&lt;br /&gt;
LOADN R8 3&lt;br /&gt;
CALL R6 2 1&lt;br /&gt;
LOADK R5 K6 [&#039;New val is &#039;]&lt;br /&gt;
CONCAT R4 R5 R6&lt;br /&gt;
CALL R3 1 0&lt;br /&gt;
RETURN R2 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Okay, that&#039;s pretty decent. Some places where things could be more optimized, but no big deal. The equivalent Mono bytecode is (and bear with us, this is long:)&lt;br /&gt;
&lt;br /&gt;
{| role=&amp;quot;presentation&amp;quot; class=&amp;quot;wikitable mw-collapsible autocollapse mw-collapsed&amp;quot;&lt;br /&gt;
| &amp;lt;strong&amp;gt;Mono bytecode&amp;lt;/strong&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre&amp;gt;&lt;br /&gt;
 .class public auto ansi serializable beforefieldinit&lt;br /&gt;
  LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    extends [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 &#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 val&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public float32 &#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
  .field public int32 &#039; l1&#039;&lt;br /&gt;
&lt;br /&gt;
  .method compilercontrolled specialname rtspecialname instance void&lt;br /&gt;
    .ctor(&lt;br /&gt;
      int32 _param1,&lt;br /&gt;
      class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 _param2,&lt;br /&gt;
      float32 _param3,&lt;br /&gt;
      float32 _param4,&lt;br /&gt;
      float32 _param5,&lt;br /&gt;
      int32 _param6&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 4&lt;br /&gt;
&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: call         instance void [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame::.ctor()&lt;br /&gt;
&lt;br /&gt;
    // [29 5 - 29 28]&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldarg.s      _param1&lt;br /&gt;
    IL_0009: nop&lt;br /&gt;
    IL_000a: nop&lt;br /&gt;
    IL_000b: nop&lt;br /&gt;
    IL_000c: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
&lt;br /&gt;
    // [30 5 - 30 34]&lt;br /&gt;
    IL_0011: ldarg.0      // this&lt;br /&gt;
    IL_0012: ldarg.s      _param2&lt;br /&gt;
    IL_0014: nop&lt;br /&gt;
    IL_0015: nop&lt;br /&gt;
    IL_0016: nop&lt;br /&gt;
    IL_0017: stfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 23]&lt;br /&gt;
    IL_001c: ldarg.0      // this&lt;br /&gt;
    IL_001d: ldarg.s      _param3&lt;br /&gt;
    IL_001f: nop&lt;br /&gt;
    IL_0020: nop&lt;br /&gt;
    IL_0021: nop&lt;br /&gt;
    IL_0022: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
&lt;br /&gt;
    // [32 5 - 32 23]&lt;br /&gt;
    IL_0027: ldarg.0      // this&lt;br /&gt;
    IL_0028: ldarg.s      _param4&lt;br /&gt;
    IL_002a: nop&lt;br /&gt;
    IL_002b: nop&lt;br /&gt;
    IL_002c: nop&lt;br /&gt;
    IL_002d: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
&lt;br /&gt;
    // [33 5 - 33 28]&lt;br /&gt;
    IL_0032: ldarg.0      // this&lt;br /&gt;
    IL_0033: ldarg.s      _param5&lt;br /&gt;
    IL_0035: nop&lt;br /&gt;
    IL_0036: nop&lt;br /&gt;
    IL_0037: nop&lt;br /&gt;
    IL_0038: stfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
&lt;br /&gt;
    // [34 5 - 34 28]&lt;br /&gt;
    IL_003d: ldarg.0      // this&lt;br /&gt;
    IL_003e: ldarg.s      _param6&lt;br /&gt;
    IL_0040: nop&lt;br /&gt;
    IL_0041: nop&lt;br /&gt;
    IL_0042: nop&lt;br /&gt;
    IL_0043: stfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0048: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor&lt;br /&gt;
&lt;br /&gt;
  .method public virtual instance object&lt;br /&gt;
    Resume() cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 8&lt;br /&gt;
&lt;br /&gt;
    // [37 37 - 37 99]&lt;br /&gt;
    IL_0000: ldarg.0      // this&lt;br /&gt;
    IL_0001: ldfld        class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; instance&#039;&lt;br /&gt;
    IL_0006: ldarg.0      // this&lt;br /&gt;
    IL_0007: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::val&lt;br /&gt;
    IL_000c: ldarg.0      // this&lt;br /&gt;
    IL_000d: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039;sub&#039;&lt;br /&gt;
    IL_0012: call         instance float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575::gloudSubtract(float32, float32)&lt;br /&gt;
    IL_0017: box          [mscorlib]System.Single&lt;br /&gt;
    IL_001c: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::Resume&lt;br /&gt;
} // end of class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 // ... Within the actual script class&lt;br /&gt;
&lt;br /&gt;
 .method public hidebysig instance float32&lt;br /&gt;
    gloudSubtract(&lt;br /&gt;
      float32 val,&lt;br /&gt;
      float32 &#039;sub&#039;&lt;br /&gt;
    ) cil managed&lt;br /&gt;
  {&lt;br /&gt;
    .maxstack 16&lt;br /&gt;
    .locals init (&lt;br /&gt;
      [0] float32 num1,&lt;br /&gt;
      [1] int32 &#039;l1[34-35], num2[69-151], num2[151-152]&#039;,&lt;br /&gt;
      [2] class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame c9eec9e37575gloudSubtractFrame,&lt;br /&gt;
      [3] float32 num3&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    // [17 5 - 17 60]&lt;br /&gt;
    IL_0000: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsRestoring()&lt;br /&gt;
    IL_0005: brfalse      IL_0036&lt;br /&gt;
&lt;br /&gt;
    // [19 7 - 19 202]&lt;br /&gt;
    IL_000a: call         class [UThread]LindenLab.SecondLife.UThread.UThreadStackFrame [UThread]LindenLab.SecondLife.UThread.UThread::Pop()&lt;br /&gt;
    IL_000f: castclass    LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0014: stloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
&lt;br /&gt;
    // [20 7 - 20 53]&lt;br /&gt;
    IL_0015: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0016: ldfld        float32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l0&#039;&lt;br /&gt;
    IL_001b: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [21 7 - 21 55]&lt;br /&gt;
    IL_001c: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_001d: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; l1&#039;&lt;br /&gt;
    IL_0022: stloc.1      // l1&lt;br /&gt;
&lt;br /&gt;
    // [22 7 - 22 55]&lt;br /&gt;
    IL_0023: ldloc.2      // c9eec9e37575gloudSubtractFrame&lt;br /&gt;
    IL_0024: ldfld        int32 LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::&#039; pc&#039;&lt;br /&gt;
    IL_0029: switch       (IL_0085, IL_004b)&lt;br /&gt;
&lt;br /&gt;
    // [31 5 - 31 58]&lt;br /&gt;
    IL_0036: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_003b: brfalse      IL_004b&lt;br /&gt;
&lt;br /&gt;
    // [33 7 - 33 15]&lt;br /&gt;
    IL_0040: ldc.i4       1 // 0x00000001&lt;br /&gt;
    IL_0045: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [34 7 - 34 20]&lt;br /&gt;
    IL_0046: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [37 5 - 37 70]&lt;br /&gt;
    IL_004b: ldarg.s      &#039;sub&#039;&lt;br /&gt;
    IL_004d: nop&lt;br /&gt;
    IL_004e: nop&lt;br /&gt;
    IL_004f: nop&lt;br /&gt;
    IL_0050: ldarg.s      val&lt;br /&gt;
    IL_0052: nop&lt;br /&gt;
    IL_0053: nop&lt;br /&gt;
    IL_0054: nop&lt;br /&gt;
    IL_0055: call         float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&lt;br /&gt;
    IL_005a: stloc.0      // num1&lt;br /&gt;
&lt;br /&gt;
    // [38 5 - 38 84]&lt;br /&gt;
    IL_005b: ldloc.0      // num1&lt;br /&gt;
    IL_005c: call         string [LslLibrary]LindenLab.SecondLife.LslRunTime::ToString(float32)&lt;br /&gt;
    IL_0061: ldstr        &amp;quot;New val is &amp;quot;&lt;br /&gt;
    IL_0066: call         string [LslUserScript]LindenLab.SecondLife.LslUserScript::Add(string, string)&lt;br /&gt;
    IL_006b: call         void [LslLibrary]LindenLab.SecondLife.Library::llOwnerSay(string)&lt;br /&gt;
&lt;br /&gt;
    // [39 5 - 39 58]&lt;br /&gt;
    IL_0070: call         bool [UThread]LindenLab.SecondLife.UThread.UThread::IsSaveDue()&lt;br /&gt;
    IL_0075: brfalse      IL_0085&lt;br /&gt;
&lt;br /&gt;
    // [41 7 - 41 15]&lt;br /&gt;
    IL_007a: ldc.i4       0 // 0x00000000&lt;br /&gt;
    IL_007f: stloc.1      // num2&lt;br /&gt;
&lt;br /&gt;
    // [42 7 - 42 20]&lt;br /&gt;
    IL_0080: br           IL_0087&lt;br /&gt;
&lt;br /&gt;
    // [45 5 - 45 17]&lt;br /&gt;
    IL_0085: ldloc.0      // num1&lt;br /&gt;
    IL_0086: ret&lt;br /&gt;
    IL_0087: nop&lt;br /&gt;
&lt;br /&gt;
    // [47 5 - 47 194]&lt;br /&gt;
    IL_0088: ldloc.1      // num2&lt;br /&gt;
    IL_0089: ldarg.0      // this&lt;br /&gt;
    IL_008a: ldarg        val&lt;br /&gt;
    IL_008e: nop&lt;br /&gt;
    IL_008f: nop&lt;br /&gt;
    IL_0090: ldarg        &#039;sub&#039;&lt;br /&gt;
    IL_0094: nop&lt;br /&gt;
    IL_0095: nop&lt;br /&gt;
    IL_0096: ldloc.0      // num1&lt;br /&gt;
    IL_0097: ldloc.1      // num2&lt;br /&gt;
    IL_0098: newobj       instance void LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575gloudSubtractFrame::.ctor(int32, class LSL_a26e2aa2_c02b_c23e_06be_c9eec9e37575, float32, float32, float32, int32)&lt;br /&gt;
    IL_009d: pop&lt;br /&gt;
&lt;br /&gt;
    // [49 5 - 49 17]&lt;br /&gt;
    IL_009e: ldloc.3      // num3&lt;br /&gt;
    IL_009f: ret&lt;br /&gt;
&lt;br /&gt;
  } // end of method&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
While this might look unreasonably large, this is also pretty similar to how &amp;lt;code&amp;gt;async&amp;lt;/code&amp;gt; C# functions that have &amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt;s get compiled. The code required to allow a function to pause in the middle to let other things run is necessarily very large, and our Mono bytecode instrumentation is essentially a special variant of that which optimizes for cases where the function won&#039;t have to pause. Try pasting the following code into sharplab.io (while decompiling to IL) and you&#039;ll see something very similar:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
using System.Threading.Tasks;&lt;br /&gt;
public class Example {&lt;br /&gt;
    public async Task llOwnerSay(string val)&lt;br /&gt;
    {&lt;br /&gt;
        // stub&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    public async Task&amp;lt;float&amp;gt; loudSubtract(float val, float sub) {&lt;br /&gt;
        float new_val = val - sub;&lt;br /&gt;
        await llOwnerSay(&amp;quot;New val is &amp;quot; + new_val.ToString());&lt;br /&gt;
        return new_val;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Lua bytecode ends up being a lot smaller, because the Lua scripting engine itself knows how to handle this case, and doesn&#039;t need to add any special code to your scripts to allow them to be paused and resumed in the middle of a function.&lt;br /&gt;
&lt;br /&gt;
Even though Mono has a JIT optimizer, which should in theory make the script run faster, this sort of code is very unusual, and the JIT optimizer isn&#039;t able to optimize it very well. That means that even though Luau itself is in theory slower, it&#039;s able to run LSL much faster than Mono can. &lt;br /&gt;
&lt;br /&gt;
You might also notice things like call &amp;lt;code&amp;gt;float64 [LslUserScript]LindenLab.SecondLife.LslUserScript::Subtract(float64, float64)&amp;lt;/code&amp;gt; in the Mono code. Those are necessary because of LSL&#039;s weird operand evaluation order, but are unnecessary in Luau because of the more efficient form of bytecode that Luau uses.&lt;br /&gt;
&lt;br /&gt;
In general, C# doesn&#039;t aim at producing very small bytecode for async functions, which is important for something like Second Life where you might have thousands of different individual scripts running within a region. Luau does generate small bytecode, while still having decent performance, making it a good choice for Second Life. And hey, strings are UTF-8 in Lua instead of UTF-16 as in Mono, so they use much less memory for most data as well.&lt;/div&gt;</summary>
		<author><name>Signal Linden</name></author>
	</entry>
</feed>