Introduction
godot-luau-script
is a GDExtension adding support for Roblox’s
Luau, a variant of Lua 5.1, to Godot 4.x as a
scripting language.
Priorities
This project has a few specific goals:
- Parity with Godot’s official scripting languages (GDScript, C#)
- Allow for (relatively) safe loading of user-generated content (UGC) including
scripts
- Sandbox “core” and “user” scripts from each other (separate Lua VMs)
- Enforce permission levels for restricted APIs, such as networking or filesystem access
- Limit breakage of the game by user scripts
- Scan PCKs for unsafe script or resource access
- Support typechecking of source files
Note that godot-luau-script
was specifically built for loading untrusted UGC.
As such, other languages like GDScript may be more suitable if UGC support is
not needed (that is, unless you are partial to Luau as a language).
Disclaimer
This project is still in relatively early stages. Please keep in mind:
- (Potentially major) breaking changes will happen often and whenever necessary.
- This project will usually track the latest Godot version (including preview releases), and support for older versions is not planned.
- This documentation may be incomplete or out-of-date. Please report an issue if this is the case.
- Some security issues may remain. Please report an issue if you encounter one.
Building and Installation
Download
Prebuilt binaries for each tagged release can be found on the releases page.
Installation
All necessary GDExtension files are provided in bin/
. It should suffice to
copy the entire folder into your Godot project. After reloading the project, the
extension should be loaded and you should be able to create a Luau script
through the script creation dialog.
Building yourself
godot-luau-script
uses SConstruct to build.
Cloning
The three dependencies this project has, godot-cpp, Luau, and Catch2, are added
as submodules in extern/
. As such, make sure to clone submodules when cloning
this project:
git clone https://github.com/Fumohouse/godot-luau-script --recurse-submodules
, orgit submodule update --recursive
if the repository is already cloned.
Supported platforms
godot-luau-script
supports compilation using recent versions of Clang, GCC,
MinGW, and MSVC, and building for Windows, macOS, and Linux. LLVM (clang++
,
clangd
, clang-format
) utilities are preferred where possible.
Building
Ensure that a supported toolchain is installed. You will also need to install
Python 3 and run python -m pip install scons
to
install SConstruct. If on Linux, SConstruct is also available through some
package repositories.
If you are running a Python version older than 3.11, you will need to
python -m pip install tomli
for TOML support.
Build the project by running scons
at the project root. The following flags
are supported:
tests=yes
: Build with Catch2 tests included.iwyu=yes
: Runinclude-what-you-use
instead of the compiler to find potential#include
mistakes.generate_luau_bindings=yes
: Force regeneration of any auto-generated files (seebindgen/
).
Additionally, you may want to use the following flags from godot-cpp:
target=editor
: Build for the editor.target=template_release
: Build for release templates.use_llvm=yes
: Force usage of LLVM for compilation (over GCC).debug_symbols=yes
: Build the project with debug symbols.- Run
scons compiledb target=editor tests=yes
to generate a compilation commands database, typically for use with a language server likeclangd
.
After building, the output will be present in the bin/
folder.
Build containers
podman
build containers are provided in build_containers/
for reproducible
build environments targeting Linux, Windows, and macOS.
Usage
Scripts should not be run outside the directory they are in.
- Ensure
podman
is installed - Build images with
build_images.sh
- Build the project with
build_gls.sh
- use argumentslinux
,windows
, andmacos
to select which platform(s) to build
Preparing osxcross
Go to tpoechtrager/osxcross and follow an appropriate procedure for downloading Xcode and packaging the SDK on your system.
Place the output SDK (expected name: MacOSX13.3.sdk.tar.xz
) in
build_containers/files/
.
Build was tested on Xcode 14.3.1 (SDK: macOS 13.3).
Accessing Godot APIs
The majority of builtin and Object
classes are bound from Godot to Luau. A
selection of utility functions from Godot are also available.
To find out what APIs are exposed, you can refer to the
definitions/luauScriptTypes.gen.d.lua
file after building (beware: this file
is large!) or rely on autocomplete to tell
you.
For the most part, you can refer to Godot’s documentation to discover the API. After learning the rules below, this should become relatively intuitive.
Renaming rules
Most names are adapted from their Godot counterparts according to the following convention:
Type | Godot Case | Luau Case |
---|---|---|
Classes and enums | PascalCase | unchanged |
Utility functions | snake_case | unchanged with few exceptions |
Enum values | PREFIX_UPPER_SNAKE_CASE | UPPER_SNAKE_CASE with exceptions * |
Constants | UPPER_SNAKE_CASE | unchanged |
Methods | snake_case | PascalCase |
Properties/signals | snake_case | camelCase |
*: e.g. for the PropertyUsage
enum, the prefix on all values is
PROPERTY_USAGE_
; this is removed. Additionally, if an enum name begins with a
number after renaming (e.g. KEY_9
with prefix KEY_
-> 9
), an N
will be
prepended to the name -> N9
.
Accessible APIs
Globals
Type | Godot Access | Luau Access | GDScript Example | Luau Example |
---|---|---|---|---|
Global enum | @GlobalScope | Enum. namespace | MARGIN_LEFT | Enum.Margin.LEFT |
Global constant | @GlobalScope | Constants. namespace | N/A | N/A |
Utility function | @GlobalScope | _G | lerpf(0.0, 1.0, 0.5) | lerp(0, 1, 0.5) * |
*: This is one of the renamed functions mentioned above.
Variant and Object classes
Type | Godot Access | Luau Access | GDScript Example | Luau Example |
---|---|---|---|---|
Variant constructors | <ClassName> | <ClassName>.new() | Vector3(0, 1, 0) | Vector3.new(0, 1, 0) |
Object constructors | <ClassName>.new() | unchanged | AESContext.new() | unchanged |
Object singleton | <ClassName> | <ClassName>.singleton | ||
Static methods | <ClassName>.<Method> | unchanged | Vector2.from_angle(x) | Vector2.FromAngle(x) |
Instance methods | <Instance>.<Method> | <Instance>:<Method> | v1.dot(v2) | v1:Dot(v2) |
Member/property/signal access | <Instance>.<Property> | unchanged * | vector.x | unchanged |
Keyed/indexed set | <Instance>[<Key>] = <Value> | <Instance>:Set(<Key>, <Value>) | dictionary["key"] = 1 | dictionary:Set("key", 1) |
Keyed/indexed get | <Instance>[<Key>] | <Instance>:Get(<Key>) | dictionary["key"] | dictionary:Get("key") |
Array length | <Array>.size() | <Array>:Size() OR #<Array> | array.size() | array:Size() OR #array |
Array iteration | for item in <Array>: | for index, item in <Array> do ** | ||
Dictionary iteration | for key in <Dictionary>: | for key, value in <Dictionary> do ** | ||
Variant type operators | <A> <Op> <B> /<Unary><A> | unchanged *** | v1 == v2 | unchanged |
Variant/Object to string | str(<Instance>) | tostring(<Instance>) |
*: Variant type properties (e.g. Vector2.x
) cannot be set because Luau
does not support copy on assign (as C++ and GDScript do). You must construct a
new object instead.
**: Iterators do not support modification during iteration. Doing so may cause
errors or for items to be skipped.
***: Some Godot operators are not supported as they do not exist in Luau. Also,
==
comparison is not allowed between two different types in Luau, so these
operators do not work.
Notes
- For security reasons, the only supported
Callable
constructor isCallable.new(object: Object, methodName: string | StringName)
. String
,StringName
, andNodePath
are not bound to Luau as Luau’s builtinstring
suffices in the majority of cases.StringName
andNodePath
can be constructed manually if needed (e.g. if aString
type would be inferred over the other two types) by using theSN
orNP
global functions respectively.- The intention with these constructors is to use them like a prefix you would
find in other languages, e.g.
SN"testStringName"
. This is valid because of Lua’s parsing rules.
- The intention with these constructors is to use them like a prefix you would
find in other languages, e.g.
- Some Lua types are automatically converted to Godot types when necessary:
{Variant}
toArray
{T}
to their correspondingPacked[T]Array
types{[Variant]: Variant}
toDictionary
string
toString
,NodePath
, orStringName
Class Registration
Class registration allows creating custom script classes derived from Godot classes or other script classes.
Annotations
Class registration in godot-luau-script
is primarily done through annotations,
an extension of Luau’s syntax expressed with comments.
Annotation comments begin with three dashes in a row, and must have no code on the same line. A block of annotation comments is delimited by a completely blank line, and the code the annotation refers to is on the line after the last comment line.
Annotations begin with an @
followed by a camelCase
name. Where there are
multiple arguments, they are separated by spaces.
An optional text comment can precede the annotation comments. This comment is currently parsed but not used for anything.
--- This is a comment. Documentation can go here.
---
--- The empty comment above indicates a line break.
--- @annotation1 arg1 arg2 arg3
--- @annotation2 arg1 arg2 arg3
local x = 1 -- The above comment block is attached to this line.
--- This is a comment.
--- This is a different comment since the above line is empty.
-- This is not an annotation comment.
--[[
This is not an annotation comment.
]]
print("hello world!") --- This is not an annotation comment.
Defining a Class
godot-luau-script
classes are essentially a special table created using
gdclass
.
Both the original table and the output of gdclass
are expected to be stored
in locals, typically at the start of the file.
Annotations used for class registration, such as @class
, should be placed
on the original table local and not the local storing the output of gdclass
.
The output of gdclass
should be returned at the end of the file.
Valid example:
--- @class
local MyClass = {}
local MyClassC = gdclass(MyClass)
return MyClassC
Invalid example:
--- @class
MyClass = {} -- INVALID: The class parser ignores globals
return gdclass(MyClass) -- INVALID: The class parser will not trace this return
Annotation Reference
Key for documentation:
[]
denotes an optional argument<>
denotes a required argument...
means an argument can be supplied multiple times|
means ‘or’''
denotes a literal argument supplied to the annotation without quotes{}
denotes multiple choices at any given location
Class Definition
These annotations should be attached to the local defining the original class
table (in this example, MyClass
).
@class [globalClassName]
Required to define a class.
globalClassName
: If supplied, the class will be globally visible to Godot.
@extends <base>
Defines a script’s base class.
base
: Either a valid Godot class name, or the name of a local containing a directlyrequire
d script.
require
example:
-- Your require call must directly contain a path.
-- It must appear before the @extends annotation.
local BaseClass = require("BaseClass.lua")
--- @class
--- @extends BaseClass
local MyClass = {}
-- <truncated>
@tool
Indicates the script is instantiable/runnable in the editor.
@permissions <...permissionsFlags>
Declares the script’s permissions (see “Core Scripts”, VMs, and Permissions).
permissionsFlags
: Valid permissions flags.
The following permissions flags are accepted:
BASE
: Used for functionality that is available to all scripts by default.INTERNAL
: Used for functionality that is not part of any special permission level orBASE
.OS
: Used for theOS
singleton.FILE
: Used for any filesystem functionality.HTTP
: Used for any functionality allowing HTTP listen/requests.
@iconPath <path>
Defines a script’s icon in the editor.
path
: The path to the icon. Must be absolute and point to an SVG image.
Class Type
@classType <classLocal>
This annotation should be attached to the type definition associated with your class.
Links a class to its type definition, which is used for registering properties and signals.
classLocal
: The name of the local which your@class
annotation is attached to.
Example:
--- @class
local MyClass = {}
--- @classType MyClass
export type MyClass = RefCounted & typeof(MyClass) & {
-- Attach annotations in the "Property annnotations" section here.
tableField1: number,
tableField2: string,
} & {
-- It's okay to intersect multiple table types.
-- Annotations will still be parsed.
tableField3: Vector2,
}
-- <truncated>
Only plain table types and intersections (&
, not |
) are supported.
Property annotations
These annotations should be attached to the individual properties in your class type.
Property groups
These annotations are evaluated in order alongside @property
and @signal
.
@propertyGroup [groupName]
Denotes a property group visible in the Godot editor.
groupName
: The group name. If not supplied, the group for the next property is unset.
@propertySubgroup [groupName]
Denotes a subgroup of a property group visible in the Godot editor.
groupName
: The group name. If not supplied, the group for the next property is unset.
@propertyCategory [groupName]
Denotes a property category visible in the Godot editor.
groupName
: The group name. If not supplied, the category for the next property is unset.
Properties
The following annotations apply only to properties.
@property
Indicates a property should be exposed to Godot, registered based on its type annotation. All additional information necessary for the property is supplied through other annotations.
@default <defaultValue>
Registers a property’s default value, which will be automatically assigned when a script instance is initialized if no setter or getter exists.
defaultValue
: The default value, in a format compatible with Godot’sstr_to_var
.
@set <method>
and @get <method>
Register a property’s setter and getter, respectively.
method
: The name of the setter/getter. The method should be part of this class and registered to Godot.
@range <min> <max> [step] [...'orGreater'|'orLess'|'hideSlider'|'radians'|'degrees'|'radiansAsDegrees'|'exp'|'suffix:'<suffix>]
Valid only for integer
and number
properties. Defines the range (and
optional step) for a numeric value in the Godot editor.
Do not use decimals (including .0
) for integer
properties.
min
: The minimum value.max
: The maximum value.step
: The step value.orGreater
andorLess
: Allow the limit to apply only to the slider.hideSlider
: Hide the slider.radians
anddegrees
: Indicate the units of an angle value.radiansAsDegrees
: Indicate the value is in radians but should be displayed as degrees in the editor.exp
: Cause values to change exponentially.suffix:<suffix>
: Provide a custom suffix for the value.
@enum <...values>
Valid only for integer
and string
properties. Defines a set of acceptable
values for a property in the Godot editor, either by index (for integer
properties) or name (for string
properties).
values
: The enum values.
@suggestion <...values>
Valid only for string
properties. Defines a set of suggested values for a
property in the Godot editor.
values
: The suggested values.
@flags <...flagValues>
Valid only for integer
properties. Defines a set of bit flags which are easily
set in the Godot editor.
flagValues
: The names of the flag values.
@file ['global'] [...extensions]
Valid only for string
properties. Indicates to the Godot editor that a string
should be a file path.
global
: If supplied, the path is not constrained to the current Godot project.extensions
: Desired extensions in the format*.ext
, for example*.png
and*.jpeg
.
@dir ['global']
Valid only for string
properties. Indicates to the Godot editor that a string
should be a directory path.
global
: If supplied, the path is not constrained to the current Godot project.
@multiline
Valid only for string
properties. Indicates to the Godot editor that a string
should have a multiline editor.
@placeholderText <text>
Valid only for string
properties. Indicates to the Godot editor that a certain
string should be displayed in the property’s text field if it is empty.
text
: The placeholder text.
@flags{2D,3D}{Render,Physics,Navigation}Layers
Valid only for integer
properties. Indicate to the Godot editor that one of
the various layer editors should be shown in place of the default number editor.
@expEasing ['attenuation'|'positiveOnly']
Valid only for number
properties. Indicates to the Godot editor that a easing
curve editor should be shown in place of the default number editor.
attenuation
: Flips the curve horizontally.positiveOnly
: Limit values to those greater than or equal to zero.
@noAlpha
Valid only for Color
properties. Indicates to the Godot editor that the color
should not have an editable alpha channel.
@signal
Indicates a property with the Signal
or SignalWithArgs<F>
type should be registered as a signal.
Methods
The following annotations should be attached to methods defined directly on
the table indicated by @class
. For example:
-- <truncated>
--- @registerMethod
function MyClass.TestMethod()
end
The following is not currently valid:
-- <truncated>
-- Not recognized
--- @registerMethod
MyClass.TestMethod = function()
end
@registerMethod
Indicates a method should be accessible to Godot, registered based on its type annotations.
If there are no type annotations, Variant
is assumed for all arguments,
and the return value is Variant
if the method returns anything.
@param <name> [comment]
Indicates a parameter’s usage. Not currently used by godot-luau-script
.
name
: The name of the parameter.comment
: Comment describing the parameter.
@defaultArgs <array>
Indicates a method’s default arguments when called from Godot.
array
: The default values, in the format of an array compatible with Godot’sstr_to_var
. The rightmost value corresponds to the rightmost argument.
@return [comment]
The @return
annotation documents a return value. It is not currently used by
godot-luau-script
.
comment
: Comment describing the return value.
@flags [...methodFlags]
Overrides a method’s flags.
See the Godot documentation
for a list of flags and their meanings (omit METHOD_FLAG_
).
methodFlags
: The flag values.
@rpc ['anyPeer'|'authority'] ['unreliable'|'unreliableOrdered'|'reliable'] ['callLocal'] [channel]
Registers a remote procedure call with Godot.
See the this Godot blog post for details.
anyPeer
: Allow the RPC to be called by any peer.authority
: Only allow the RPC to be called by the multiplayer authority.unreliable
: Transfer data unreliably without resending.unreliableOrdered
: Transfer data unreliably without resending, but in the correct order. Any older packets are dropped. This flag should not be used for different types of messages on the same channel.reliable
: Transfer data reliably; resend packets if they don’t arrive.callLocal
: Indicate this method must also be called locally when the RPC is sent.channel
: Indicate the channel this RPC should use. Each channel handles ordering of packets separately.
Assigns
The following annotations should be attached to assignments to fields on the
table denoted by @class
. For example:
-- <truncated>
--- @registerConstant
MyClass.TEST_CONSTANT = 5
@registerConstant
Valid only for assignments with Variant
values. Registers a constant value
with Godot.
Type Conversion
godot-luau-script
does its best to determine the proper Godot type from Luau
type annotations. Here are a few things to note about this process:
Nullable (?
or T | nil
) types
When annotating a method argument with a nullable type, the argument will be
registered to Godot as non-nullable (i.e. number?
-> FLOAT
). If you want to
have the argument be optional on the Godot side, register default arguments.
When annotating anything else (properties, return types, etc.), using a nullable
type will cause the type to be registered with Godot as Variant
unless the
type extends Object
. This is because the only Godot type that accepts both
a value and null
is Variant
. If you encounter this, it’s recommended to
reimplement your logic such that using a nullable is not necessary.
TypedArray<T>
When used for methods and properties, TypedArray<T>
will supply Godot with
the correct type for Array
elements.
NodePathConstrained<T...>
When used for methods and properties, NodePathConstrained<T...>
will tell
Godot to constrain valid Node
types to the given types. Only supply types
which extend Node
.
SignalWithArgs<F>
When used for signals, SignalWithArgs<F>
will tell Godot the parameters of the
registered signal. The supplied type F
should be a void (-> ()
) function
type with no generics and any number of arguments.
Argument names are optional. If not supplied, the default is argN
where N
is the index of the argument starting from 1.
Other potential issues
- Aliasing Godot types (e.g.
type D = Dictionary
) is not currently supported.
Virtual Methods
godot-luau-script
supports the overriding of virtual methods.
Defined in an Object
class
If a virtual method such as _process
or _ready
is defined on an
Object
-type class, you can override it by defining it in your class table and
registering it to Godot.
Built into godot-luau-script
This project comes with a number of built-in virtual methods, which can be overridden just by defining them on your implementation table. They do not need to be registered to Godot.
function _Init(self): ()
Called as soon as an Object
with your script is instantiated.
At this time, you can initialize any values on the object or its underlying
table. Note that the virtual _Ready
on Node
may be equally or more
appropriate in many cases.
function _Notification(self, what: number): ()
Called whenever your object receives a notification. Refer to Godot documentation to see what notifications you may receive.
function _ToString(self): string
Called whenever Godot wants to stringify your object, for example when printing
your object or calling tostring
. It should return the string representation of
your object.
Custom property methods
These methods should be used in combination to define custom properties for your
object, typically so they can show up in the editor. Keep in mind you must set
@tool
on your class for these methods to run
in the editor.
function _GetPropertyList(self): {GDProperty}
Return a list of additional properties to be registered to Godot.
function _Set(self, name: string, value: Variant): boolean
Handle setting a value for a custom property. It should return true
when it
set the value successfully, and false
when it did not (or when the property
doesn’t exist).
function _Get(self, name: string): Variant
Handle getting a value for a custom property. It should return the value of the
property or nil
if the property doesn’t exist.
function _PropertyCanRevert(self, name: string): boolean
Return true
if the given property has a default value which the editor should
be able to revert to.
function _PropertyGetRevert(self, name: string): Variant
Overridden along with _PropertyCanRevert
. It should return the default value
of the given property, or nil
if it doesn’t exist.
The “Idiomatic” Class
As this project is still in early stages, you shouldn’t consider this syntax/interface stable yet.
The following class provides an example as to what is currently considered “idiomatic” and type checkable:
--!strict
--- @class MyClass
--- @extends Resource
local MyClass = {}
-- 'C' stands for class. The `gdclass` method gives access to the `.new
-- function.
local MyClassC = gdclass(MyClass)
-- These items registered will be accessible by indexing `MyClass`,
-- making them accessible to other scripts if they `require` this one.
MyClass.TestEnum = {
ONE = 1,
TWO = 2,
THREE = 3,
}
--- @registerConstant
MyClass.TEST_CONSTANT = "hello!"
--- @classType MyClass
export type MyClass = Resource & typeof(MyClassImpl) & {
--- @property
--- @range 0.0 1.0 0.1
--- @default 0.5
testProperty: number,
}
--- Register a method to be accessible to Godot, based on its type annotations
--- @registerMethod
--- @param arg1 Comment 1
--- @param arg2 Comment 2
--- @return Something
function MyClass.TestMethodAST(self: MyClass, arg1: number, arg2: TypedArray<Resource>): Variant
return 123
end
return MyClassC
godot-luau-script
API Reference
godot-luau-script
provides its own library on top of bindings to Godot in
order to allow custom class creation and provide various other useful functions
(e.g. wait
and require
).
Currently, this library is defined and documented in
definitions/luauLibTypes.part.d.lua
.
---------------------
-- LUAGD_LIB TYPES --
---------------------
--- Constructs a new `StringName`.
--- @param str The string to use.
declare function SN(str: string): StringNameN
--- Constructs a new `NodePath`.
--- @param str The string to use.
declare function NP(str: string): NodePathN
-- TODO: constrain to Resource type?
--- Loads a resource. If the current script is not a core script, accessible
--- paths are restricted by SandboxService.
--- @param path The relative/absolute path to the resource from this script.
declare function load<T>(path: string): T?
--- Saves a resource. If the current script is not a core script, accessible
--- paths are restricted by SandboxService.
--- @param path The absolute path to save the resource to.
declare function save(resource: Resource, path: string, flags: ClassEnumResourceSaver_SaverFlags?)
--- Determines the Godot Variant type of a value, or `nil` if the value is not Variant-compatible.
--- @param value The value.
declare function gdtypeof(value: any): EnumVariantType?
--------------------
-- LUAU_LIB TYPES --
--------------------
--- A table type used to declare properties, method arguments, and return values.
export type GDProperty = {
type: EnumVariantType?,
name: string?,
hint: EnumPropertyHint?,
hintString: string?,
usage: EnumPropertyUsageFlags?,
className: string?,
}
--- A type returned from a script to register a custom class type.
declare class GDClass
--- Constructs a new instance of the base object with this script.
new: () -> Object
end
--- Creates a new @see GDClass.
--- @param tbl The implementation table for any methods, constants, etc. Items in this table will be accessible by indexing this class definition.
declare function gdclass<T>(tbl: T): T & GDClass
--- Yields the current thread and waits before resuming.
--- @param duration The duration to wait. If the engine is affected by a time factor, this duration will be affected by it.
declare function wait(duration: number): number
--- Yields the current thread and waits for the signal to be emitted before resuming.
--- @param signal The signal.
--- @param timeout The number of seconds to wait before timing out (default: 10 seconds).
--- @return The first return value is whether the signal was emitted (true) or timed out (false), and subsequent values are the arguments passed to the signal when it was emitted.
declare function wait_signal<T...>(signal: Signal, timeout: number?): (true, T...) | false
--- Gets a global constant (e.g. AutoLoad) which was defined in the Godot editor.
--- @param name The name of the global constant.
declare function gdglobal(name: StringNameLike): Variant
--------------
-- SERVICES --
--------------
export type EnumPCKScanError = number
declare class EnumPCKScanError_INTERNAL
OK: EnumPCKScanError
FILE_ERR: EnumPCKScanError
MAGIC_NOT_FOUND_ERR: EnumPCKScanError
PACK_VERSION_ERR: EnumPCKScanError
GODOT_VERSION_ERR: EnumPCKScanError
PACK_ENCRYPTED_ERR: EnumPCKScanError
FILE_SCAN_ERR: EnumPCKScanError
end
export type EnumPCKFileScanError = number
declare class EnumPCKFileScanError_INTERNAL
OK: EnumPCKFileScanError
FILE_ENCRYPTED_ERR: EnumPCKFileScanError
UNTRUSTED_GDSCRIPT_ERR: EnumPCKFileScanError
UNTRUSTED_FILE_ERR: EnumPCKFileScanError
ENDIANNESS_ERR: EnumPCKFileScanError
RES_VERSION_ERR: EnumPCKFileScanError
GODOT_VERSION_ERR: EnumPCKFileScanError
SANDBOX_VIOLATION_ERR: EnumPCKFileScanError
end
export type EnumSandboxViolations = number
declare class EnumSandboxViolations_INTERNAL
UNTRUSTED_EXT_SCRIPT_VIOLATION: EnumSandboxViolations
RESOURCE_SANDBOX_VIOLATION: EnumSandboxViolations
UNTRUSTED_INT_SCRIPT_VIOLATION: EnumSandboxViolations
end
--- Service handling script sandboxing
declare class SandboxService
--- Returns whether a script is a core script.
--- @param path The path to query.
function IsCoreScript(self, path: string): boolean
--- Initiates core script discovery from the project root. By default, any
--- files present at this time will be considered core scripts.
function DiscoverCoreScripts(self)
--- Ignores a path from core script discovery. Any file paths starting with
--- the given path will be ignored.
--- @param path The path to ignore.
function CoreScriptIgnore(self, path: string)
--- Adds a script path as a core script.
--- @param path The path to add.
function CoreScriptAdd(self, path: string)
--- Returns an array of all core scripts.
function CoreScriptList(self): Array
--- Adds access for non-core scripts to read or write resources to the given
--- path and its subdirectories.
--- @param path The path to add.
function ResourceAddPathRW(self, path: string)
--- Adds access for non-core scripts to read from the given path and its
--- subdirectories.
--- @param path The path to add.
function ResourceAddPathRO(self, path: string)
--- Removes access for non-core scripts to read or write from the given path
--- and its subdirectories.
--- @param path The path to remove.
function ResourceRemovePath(self, path: string)
--- Scans a PCK file and returns a report with details.
--- @param path The path of the PCK to scan.
function ScanPCK(self, path: string): Dictionary
end
--- Service handling debugging, GC diagnostics, and profiling
declare class DebugService
--- Returns memory usage, in kibibytes (=1024 bytes), of each running VM.
function GCCount(self): PackedFloat64Array
--- Returns the current rate at which the garbage collector is attempting
--- to collect on each VM, in KB/s, roughly corresponding to the rate at
--- which memory usage is increasing.
function GCStepSize(self): PackedInt32Array
--- Executes code on the core VM with all permissions enabled.
--- IMPORTANT: This method should not be used outside of debugging (i.e.
--- core game logic should not depend on this functionality).
--- @param src The source code to execute.
--- @return An empty string if the call was successful, or the error message if any.
function Exec(self, src: string): string
end
--- The main service through which all services are found.
declare LuauInterface: {
SandboxService: SandboxService,
DebugService: DebugService,
}
“Core Scripts”, VMs, and Permissions
These three concepts are essential to godot-luau-script
’s sandboxing and
security.
“Core Scripts”
Core scripts have special privileges such as being able to set their own permissions. By default, no scripts are considered core scripts.
Scripts can be designated as core scripts through the SandboxService
,
typically using init.lua
.
VMs
godot-luau-script
currently runs 3 different Luau VMs for different use cases:
- The loading VM, which is used to load essential script information (e.g. a list of methods and constant values).
- The core VM, which is where all core scripts are executed.
- The user VM, which is where all non-core scripts are executed.
This provides a certain level of isolation and security in addition to thread sandboxing.
Method calls
Within the same VM, any script can call any other script’s methods, so long as they are defined in the implementation table. Between VMs, scripts can only call other scripts’ methods if they are registered to Godot. This gives a rough notion of “public” and “private”.
Permissions
As mentioned in the introduction, godot-luau-script
was primarily created to
load untrusted scripts safely. This means that certain Godot APIs must be locked
behind special permissions, and that scripts will need to declare these
permissions to receive them.
The specific permission levels are listed here. As noted above, only a core script can declare its own permissions.
Certain APIs receive special permissions, with INTERNAL
being the default.
Permissions are set on the thread level, and are inherited to child threads.
If a thread ever tries to execute a method or other code of a permission level
it does not have, it will cause an error.
Note that only methods which are registered to Godot are executed on their script’s thread. If any non-registered methods use protected APIs, classes with Godot-registered methods that use them must declare the necessary permissions.
A comprehensive list of the permissions assigned to every Object class and
method can be seen at bindgen/class_settings.toml
.
Modules
godot-luau-script
supports non-class module scripts and the require
function.
require
The require
function accepts a path relative to the current script, without
the .lua
extension. It will load the file at the given path and return
whatever the file returned.
Requires are cached within the same Lua VM, and are reloaded when necessary (if the script changes).
Please note that cyclic dependencies are not supported and may cause odd behavior in the editor if encountered.
Module scripts
require
can require a script (class) file, in which case it will return the
output of gdclass
for the class.
However, you may want to create your own Lua types which don’t need to be
accessible to Godot. To do this in a dedicated file, create a file with the
extension .mod.lua
which returns a function or a table.
Then, you will be able to require this file (being sure to include the .mod
but not the .lua
in the require path).
Task Scheduler
godot-luau-script
borrows the task scheduler (among other concepts) from
Roblox.
Currently, the task scheduler is only responsible for resuming yielded threads and running the garbage collector.
It updates once every frame.
init.lua
If res://init.lua
exists, it is run with on the core
VM with all permissions enabled. Certain functionality, such as require
and
gdclass
, are disabled as init.lua
is not considered a script file.
This file can be used to configure features which can or should be initialized as soon as the extension is initialized, such as core script detection and sandboxing settings.
Typechecking and Autocomplete
Upon building, godot-luau-script
will generate a single type definition file
for use by Luau’s analyzer.
Autocomplete and analysis in the Godot editor is not supported. As such, the most viable setup is to use Visual Studio Code and the luau-lsp extension.
Setup
After installing VSCode and luau-lsp, you will need to do the following setup:
- Set the preference
luau-lsp.require.mode
torelativeToFile
to makerequire
relative to the current script. - Set the preference
luau-lsp.types.roblox
tofalse
to disable Roblox types being loaded by default. - Set the preference
luau-lsp.types.definitionFiles
to an array containing the path todefinitions/luauScriptTypes.gen.d.lua
. - Set the FFlags
LuauRecursionLimit
andLuauTarjanChildLimit
to something higher than the defaults (e.g. 5000 and 20000 respectively). This can be done through theluau-lsp.fflags.override
preference.- This is necessary because the generated definition file is too large and complex for Luau to parse by default.
After doing this (and reloading the workspace), you should get type checking and
autocomplete for all Godot and godot-luau-script
types.
Known Issues
godot-luau-script
and Godot’s GDExtension script language interface are both
relatively unstable. As such, you may experience issues when using them
together.
Error: _gdvirtual__get_language_call: Required virtual method ScriptExtension::_get_language must be overridden before calling.
This is likely an upstream issue with the initialization of GDExtension classes. Scripts should run normally despite this error.
The issue is tracked here.
Tests
godot-luau-script
contains tests written using the
Catch2 framework. They can be built using
the tests=yes
flag when running scons
.
Tests can be run by running Godot in the test_project
directory with the
--luau-tests
flag. Then, Catch2 flags can be given after supplying a --
argument.
Note for Windows users
godot-luau-script
uses a symbolic link to link bin/
to test_project/bin/
.
These do not work on Windows. If you want to run tests, you will need to copy
the folder (including built binaries) yourself.
Debugging
Assuming you built godot-luau-script
with the debug_symbols=yes
flag, you
will be able to debug the project by running Godot with a debugger attached.
In case you run into issues which the debugger cannot trace, they may be caused
by Godot engine code. You can debug engine code by cloning Godot and building it
with debug symbols as well. Be sure to git checkout
the commit matching the
current supported version of Godot before building.