I’ve come across a few situations:
vector3:components() would make reading the position and normal faster.
Ray.new(originX, originY, originZ, directionX, directionY, directionZ) would make dividing rays into segments for things like projectile paths and bullet-penetration faster.
A few use-cases: Mini-map rendering, simulating many projectiles, customized physics for cars/characters/quadrupeds, simulating rainfall, light-weight invisicam and fast camera collisions.
It’s surprisingly much more efficient to create userdatas than it is to create tables filled with numbers, although it is much slower to read values. The usefulness of this case scales directly with how fast userdatas can be indexed.
Some use-cases: Neural networks, storing game data.
This is unconventional, and cframe:components() already works (assuming you don’t need to replicate the vector).
Similar to the previous case, except less unconventional.
How many collisions can be tested scales directly with how fast userdata indexing is.
Some use-cases: Safe/consistent building tools for houses, cuboid collision detection, custom raycasting.
Userdatas can be equal, but still reference different objects. For example:
local t = {}
local key1 = Vector2.new(1, 2)
local key2 = Vector2.new(1, 2)
t[key1] = true
print(t[key2])
print(t[key1])
> nil
> true
Some of the CoreScripts might still assume otherwise: BubbleChat PlayerScript 'TextSizeCache' memory leak
I don’t expect roblox to store every userdata in an internal hash table, but sometimes I need this behavior so I do it myself using weak tables.
Here’s how I might implement this for rays (ignoring hash collisions):
local hashLookup = setmetatable({}, {__mode = "v"})
local function hashGet(v)
local origin = v.Origin
local direction = v.Direction
local hash =
origin.X +
origin.Y*25.039570006870 +
origin.Z*2.4636903606487 +
direction.X*25872.624154663 +
direction.Y*404.89306915749 +
direction.Z*120.16744135364
local v2 = hashLookup[hash]
if not v2 then
hashLookup[hash] = v
return v
elseif v == v2 then
return v2
else
-- hash collision
end
end
A :GetHash() method would be more ideal for this use-case, but :components() is more useful to everyone. Regardless of performance, :components() can be much easier to type, especially for ray’s and udim2’s.
Even if in most cases these optimizations aren’t significant, I want to know I can create twice as many rays and read twice as many vector3’s before impacting performance, and thus simulate twice as many characters and twice as many bullets before impacting performance.
Seemingly-tiny optimizations become huge once they’re applied to a case who’s usefulness scales with speed.
The bottleneck often surfaces when many scripts are doing many different things on low-end devices (especially on devices like the iPhone 4S).
The performance benefits are similar in magnitude to the __namecall optimization, where the goal is to minimize trips between Lua and the engine. A potential alternative would be to implement userdatas as Lua types internally, where library accesses like ‘Vector3.new’ would need to somehow be elevated to keyword-status to make use of the global constant table that Lua uses, but that would be quite the undertaking. Lua’s speed and flexibility is a reason to use Roblox over other game engines, but Lua is still much slower than compiled machine code.
It also doesn’t hurt that these optimizations are already consistent with the API, as they’re analogous to cframe:components() and UDim2.new(0,0,0,0).