Git Product home page Git Product logo

luau-tips's Introduction

Readability and Reliability for Luau

Table of Contents

The Task Library ---------------------------------------- 2.1
Parts and MeshParts (BaseParts) ---------------------- 2.2
The Invisible Variables --------------------------------- 2.3
The more lines the better... right? --------------------- 2.4

Operator Syntax --------------------------------------- 3.1
Variables ----------------------------------------------- 3.2
Strongly Strict Typing ---------------------------------- 3.3
How_To WriteVariable NAMES ------------------------ 3.4
Paragraphs -------------------------------------------- 3.5
Nesting ----------------------------------------------- 3.6

1.0 Introduction

This is a simple guide to writing reliable and readable code that is universally known to all programmers. It includes many aspects of coding and many practices of formatting and writing code.

This guide requires basic knowledge of Luau. The guide does not explain programming in Lua, but common mistakes and problems with readability.

2.0 Deprecations and Misconceptions

Luau is a growing language, meaning that it may deprecate older APIs. It is best to avoid using older/deprecated APIs, for obvious reasons. Before you quickly update your code with the newest, fanciest APIs you should always check what changes were made.

2.1 The Task Library

https://create.roblox.com/docs/reference/engine/libraries/task Roblox introduced the Task library in August of 2021, with the Task library being used to directly talk to the game engine's task schedule of tasks to be ran.

It brought and deprecated many functions, such as:

wait(x: Number): Number -> task.wait(x: Number): Number

spawn(function) -> task.defer(function)

While wait() is fundamentally the same, it still differs from task.wait(). The difference is in the precision of the yield, coming from 1/30 of a second to 1/60 of a second with the Task Library

2.2 Parts and MeshParts (BaseParts)

While Parts and MeshParts may look like completely different instances, they are fundamentally the same, with just the MeshPart having a different, editable topology.

To create a BasePart you can use

Instance.new(ClassName, Parent)

While this may look like the best way to create an instance, it can sometimes be less efficient, if you do not know what you are doing. The primary optimisation here will be the 2nd argument when using Instance.new().

local Part = Instance.new("Part", workspace)
-- Once the parent is set the Physics engine will check if its anchored.
-- Since it isnt it will start tracking it
Part.Anchored = true
-- The part is anchored so the Physics engine will stop tracking it.
-- This will create an overhead

->

local Part = Instance.new("Part")
Part.Anchored = true

Part.Parent = workspace
-- Once the parent is set the Physics engine will check if its anchored and since it isn't it will not start tracking it.
-- This will not create any overhead

When you set the parent with the 2nd argument of Instance.new it sets its parent immediately after the instance is made, meaning that it will also render and fire off RBXLSignalEvents for when the part is created. If you edit properties after setting the parent of an instance it can cause performance drops since you are editing the properties while it has running events and is being rendered.

The proper sequence that you should set up properties/connections is:

-- Instance.new
local Part = Instance.new("Part")

-- Assign Properties
Part.Anchored = true

-- Assign Parent
Part.Parent = workspace

-- Connect Signals
Part.Touched:Connect(function(TouchedPart: BasePart)
    print(TouchedPart.Name)
end)

2.3 The Invisible Variables

Sometimes you may just not see a built-in function in Roblox and then you try making it yourself. All Roblox API (Application Programming Interface) is written in C++ so it is considerably faster than recreating it yourself in Luau.

You might just overlook some clear features that the API offers to you such as:

local Index = 0

for i, v in {} do
    Index += 1
    print(Index)
end

->

for i, v in {} do
    print(i)
end

While you are looking at what variables you should use, you should also look at what variables to hide. You should also not be scared of naming variables to something that describes them instead of using single letters such as i or v

for i, v in {} do
    v.Position += Vector3.new(1,0,0)
end

->

for _, Part: BasePart in {} do
    Part.Position += Vector3.new(1,0,0)
end

2.4 The more lines, the better... right?

No. Just no; the exact opposite applies. The less lines there are, the more readable it is (do not take this to the extreme). A lot of the time, when we are making functions we think about the task logically in our heads, so we take it one step at a time but sometimes it may be better to just take all steps at the same time. Here is a common examples on where you can improve:

    local function PointInRectangle(Point: Vector2, Rectangle: Vector2, RectangleSize: Vector2): boolean
	if point.x >= rectPos.x and point.x <= (rectPos.x + rectSize.x) then
		if point.y >= rectPos.y and point.y <= (rectPos.y + rectSize.y)  then
			return true
		end
	end
	
	return false
end

->

local function PointInRectangle(Point: Vector2, Rectangle: Vector2, RectangleSize: Vector2): boolean
	return point.x >= rectPos.x and point.x <= (rectPos.x + rectSize.x) and 
		point.y >= rectPos.y and point.y <= (rectPos.y + rectSize.y)
end

3.0 The Art Side of Coding (Syntax)

https://roblox.github.io/lua-style-guide/

While one may think that code is purely written to be as efficient as possible, it may be better to value readability over performance in some cases.


3.1 Operator Syntax

All operators have their own syntax for shortening and simplifying your code to make it more readable, such as:

Math Operators
X = X + 1  -> X += 1
X = X - 1  -> X -= 1
X = X * X  -> X *= X
X = X / X  -> X /= X

String Operators
X = X.."JoinStrings" -> X ..= "JoinStrings"

Boolean Operators:
if X == true then -> if X then
if X == false then -> if not X then

if not X == 1 then -> if X ~= 1 then

This helps to shorten the code while increasing its readability, there is no performance change between the two ways of writing the syntax. Since it is compiled into the same bytecode

3.2 Variables

There are 2 types of variables: Local Variables and Global Variables, the difference between these 2 types is the scope of their declaration. Global variables have their scopes globally, meaning that you can access them from anywhere, while Local variables are scoped inside a function block, meaning you will only be able to access them inside of that function block

Between the 2, Local variables should be used the most. Due to their performance benefits when compared to Global variables and their memory costs

Variables can have many values inside of them, some can have predetermined values such as when you are using strict typing

3.3 Strongly Strict Typing

Strict typing, or otherwise known as strong typing is a method of programming with strictly defined types for values.

To use strict typing in Luau you have to declare it at the top of your script:

-- !strict

Rest of the code goes here

The syntax for defining a variable type is:

local MyNumber: Number = 0

This can quickly become quite complex. So you can create types and reuse them such as:

type Voxel = {
	Distance: number,
	Position: Vector3,

	LifeTime: number
}

local Example: Voxel = {}

The syntax for defining types in a function is:

local function RandomDecimal(Min: Number, Max: Number): Number
    return math.random(Min * 100, Max * 100)/100
end

Here Min and Max are both numbers, and the return is also a number

3.4 How_To WriteVariable NAMES

There are many ways of writing variable names such as: PascalCase, camelCase, snake_case and many more variations.

All of them are completely fine to use but should be used consistently with your coding accent.

You can use Roblox's Luau styling guide:

  • PascalCase for class and enum-like objects
  • PascalCase for Roblox API (camelCase versions are deprecated)
  • camelCase for Local variables
  • Spell out words fully, abbreviations may save time but may also cause confusion to the reader
  • SCREAMING_SNAKE_CASE for Constant (Non-Changing) variables
  • Acronyms that represent a set (XYZ, RGB, ...) should be fully capitalized, but acronyms that do not respesent a set should only have their starting letter be capitalized (Http, Json, ...)

3.5 Paragraphs

While programming languages aren't structured as normal literacy, they still contain aspects of them. When scripting you would want to increase the readability by separating your code into chunks. These chunks would each share a similar task such as:

local Part = Instance.new("Part")
Part.Anchored = true
Part.CanCollide = false
Part.Size = Vector3.new(10,10,10)
Part.Position = Vector3.new(0,10,0)
Part.Color = Color3.fromRGB(255,255,0)
Part.Parent = workspace
Part.Touched:Connect(function(TouchedPart: BasePart)
	print(TouchedPart.Name)
end)

->

local Part = Instance.new("Part")

Part.Anchored = true
Part.CanCollide = false

Part.Size = Vector3.new(10,10,10)
Part.Position = Vector3.new(0,10,0)
Part.Color = Color3.fromRGB(255,255,0)

Part.Parent = workspace

Part.Touched:Connect(function(TouchedPart: BasePart)
	print(TouchedPart.Name)
end)

You can see how much the readability increases just by segregating it into small chunks that share a common task. While there are no clear rules for how you should separate your code, you should always focus on seperating groups that share a common task.

3.6 Nesting

Closely related to paragraphs, nesting is how many "levels" your code has. If not quickly handled, nesting can become a nightmare to look at.

Here's a small example that displays nesting and how to fix it:

Part.Touched:Connect(function(TouchedPart: BasePart)
	if TouchedPart.Parent:FindFirstChild("Humanoid") then
		if not TouchedPart.Parent:FindFirstChild("Shield") then
			TouchedPart.Parent:Destroy()
		end
	end
end)

->

Part.Touched:Connect(function(TouchedPart: BasePart)
	if not TouchedPart.Parent:FindFirstChild("Humanoid") then return end
	if TouchedPart.Parent:FindFirstChild("Shield") then return end

	TouchedPart.Parent:Destroy()
end)

Nesting should always be avoided, since it makes the code harder to read. It is best to tackle nesting before it becomes an issue.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.