Tables: Bad Habits

From Garry's Mod
(Difference between revisions)
Jump to: navigation, search
(tables are distinct, like functions.)
m (Renamed AllowedTeams to RestrictedTeams, typo, esthetic replacement)
Line 9: Line 9:
  
 
This example shows what not to do because using useless loops is inefficient.
 
This example shows what not to do because using useless loops is inefficient.
<pre>local AllowedTeams = {
+
<pre>local RestrictedTeams = {
 
TEAM_COOK,
 
TEAM_COOK,
 
TEAM_CITIZEN,
 
TEAM_CITIZEN,
Line 24: Line 24:
  
 
hook.Add( "CanPlayerEnterVehicle", "restrict vehicles", function( ply, veh )
 
hook.Add( "CanPlayerEnterVehicle", "restrict vehicles", function( ply, veh )
if table.HasValue( AllowedTeams, ply:Team() ) then
+
if table.HasValue( RestrictedTeams, ply:Team() ) then
 
if not table.HasValue( AllowedVehicles, veh:GetClass() ) then
 
if not table.HasValue( AllowedVehicles, veh:GetClass() ) then
 
return false
 
return false
Line 35: Line 35:
 
local Value = ply:Team()
 
local Value = ply:Team()
 
local HasValue = false
 
local HasValue = false
for k, v in pairs( AllowedTeams ) do
+
for k, v in pairs( RestrictedTeams ) do
 
if ( v == Value ) then
 
if ( v == Value ) then
 
HasValue = true
 
HasValue = true
Line 58: Line 58:
  
 
The next code shows how to make your tables efficient, with no loop and no function calls! It only relies on the presence of a '''key''' (never a '''value'''): that is what keys are designed for.
 
The next code shows how to make your tables efficient, with no loop and no function calls! It only relies on the presence of a '''key''' (never a '''value'''): that is what keys are designed for.
<pre>local AllowedTeams = {
+
<pre>local RestrictedTeams = {
 
[TEAM_COOK]=true,
 
[TEAM_COOK]=true,
 
[TEAM_CITIZEN]=true,
 
[TEAM_CITIZEN]=true,
Line 73: Line 73:
  
 
hook.Add( "CanPlayerEnterVehicle", "restrict vehicles", function( ply, veh )
 
hook.Add( "CanPlayerEnterVehicle", "restrict vehicles", function( ply, veh )
if AllowedTeams[ply:Team()] then
+
if RestrictedTeams[ply:Team()] then
 
if not AllowedVehicles[veh:GetClass()] then
 
if not AllowedVehicles[veh:GetClass()] then
 
return false
 
return false
Line 81: Line 81:
 
We just check the value inside the table directly by using a key! If the value is '''true''' then the key is in the table, otherwise it is '''nil''', which is equivalent to '''false''' in a condition. This process is very inexpensive and it is worth it.
 
We just check the value inside the table directly by using a key! If the value is '''true''' then the key is in the table, otherwise it is '''nil''', which is equivalent to '''false''' in a condition. This process is very inexpensive and it is worth it.
  
This example uses table containing keys that are numbers or strings, but they also can be entities, vectors, angles, etc.
+
This example uses tables containing keys that are numbers or strings, but they also can be entities, vectors, angles, etc.
  
 
== Identical keys ==
 
== Identical keys ==
Line 110: Line 110:
 
]]</pre>
 
]]</pre>
  
For instance, identical {{Type|number}}s, {{Type|string}}s and {{Type|boolean}}s are always considered as the same. But {{Type|function}}s, {{Type|table}}s, {{Type|Vector}}s, {{Type|Angle}}s and '''NULL '''{{Type|Entity}}s can seem identical but actually be distinct in memory.
+
For instance, identical {{Type|number}}s, {{Type|string}}s and {{Type|boolean}}s are always considered as the same. But {{Type|function}}s, {{Type|table}}s, {{Type|Vector}}s, {{Type|Angle}}s and '''NULL '''{{Type|Entity}}s can seem identical while actually being distinct in memory.
 
<pre>local function1 = function()
 
<pre>local function1 = function()
 
print( "hello world" )
 
print( "hello world" )

Revision as of 17:59, 28 October 2016

Manipulating tables in an inefficient way is so common that instead of complaining, I am publishing a guide.

Feel free to share this guide all around the Lua programming community, especially beginners!

The examples below do not necessarily work as they are shown. They are only given for a teaching purpose.

Example 1

Description: This example restricts the vehicle classes usable by some teams.

This example shows what not to do because using useless loops is inefficient.

local RestrictedTeams = {
	TEAM_COOK,
	TEAM_CITIZEN,
	TEAM_MOB,
}

local AllowedVehicles = {
	"prop_vehicle_jeep",
	"prop_vehicle_airboat",
	"prop_vehicle_custom1",
	"prop_vehicle_custom4",
	"prop_vehicle_custom5",
}

hook.Add( "CanPlayerEnterVehicle", "restrict vehicles", function( ply, veh )
	if table.HasValue( RestrictedTeams, ply:Team() ) then
		if not table.HasValue( AllowedVehicles, veh:GetClass() ) then
			return false
		end
	end
end )

Now let's expand every call of table.HasValue as a loop. This basically translates the function to its meaning.

hook.Add( "CanPlayerEnterVehicle", "restrict vehicles", function( ply, veh )
	local Value = ply:Team()
	local HasValue = false
	for k, v in pairs( RestrictedTeams ) do
		if ( v == Value ) then
			HasValue = true
			break
		end
	end
	if HasValue then
		local Value = veh:GetClass()
		local HasValue = false
		for k, v in pairs( AllowedVehicles ) do
			if ( v == Value ) then
				HasValue = true
				break
			end
		end
		if not HasValue then
			return false
		end
	end
end )

Now you see clearly that you loop over tables by using table.HasValue. In 99% of situations, checking the presence of a value in a table is a waste of CPU time. Using this method is very bad for small tables, and it is insane for large tables.

The next code shows how to make your tables efficient, with no loop and no function calls! It only relies on the presence of a key (never a value): that is what keys are designed for.

local RestrictedTeams = {
	[TEAM_COOK]=true,
	[TEAM_CITIZEN]=true,
	[TEAM_MOB]=true,
}

local AllowedVehicles = {
	["prop_vehicle_jeep"]=true,
	["prop_vehicle_airboat"]=true,
	["prop_vehicle_custom1"]=true,
	["prop_vehicle_custom4"]=true,
	["prop_vehicle_custom5"]=true,
}

hook.Add( "CanPlayerEnterVehicle", "restrict vehicles", function( ply, veh )
	if RestrictedTeams[ply:Team()] then
		if not AllowedVehicles[veh:GetClass()] then
			return false
		end
	end
end )

We just check the value inside the table directly by using a key! If the value is true then the key is in the table, otherwise it is nil, which is equivalent to false in a condition. This process is very inexpensive and it is worth it.

This example uses tables containing keys that are numbers or strings, but they also can be entities, vectors, angles, etc.

Identical keys

Stay aware from something though: objects can have identical values but still be distinct!

local angle1 = Angle( 90,0,0 )
local angle2 = Angle( 90,0,0 )
print( "angles are same (1):", ( angle1==angle2 ) )
angle2.p = 180
print( "angles are same (2):", ( angle1==angle2 ) )
--[[ Output:
angles are same (1):	true
angles are same (2):	false
]]

Despite these angles being identical, they are 2 distinct objects in memory as you can see!

local vector1 = Vector( 100,0,0 )
local vector2 = Vector( 100,0,0 )

local TestTable = {}
TestTable[vector1]=true
TestTable[vector2]=true

for vector in pairs( TestTable ) do
	print( "vector is", vector )
end
--[[ Output:
vector is	100	0	0
vector is	100	0	0
]]

For instance, identical numbers, strings and booleans are always considered as the same. But functions, tables, Vectors, Angles and NULL Entitys can seem identical while actually being distinct in memory.

local function1 = function()
	print( "hello world" )
end
local function2 = function()
	print( "hello world" )
end

print( function1 )
print( function2 )
--[[ Output:
function: 0x30afbb60
function: 0x30afde20
]]
Personal tools
Navigation