Native Luau Vector3 Beta

Thank you for the reply. What would be a better way to implement a proper serialization, then? I had previously been doing if getfenv()[Class] then, but due to optimizations being disabled when using getfenv, I switched to type(x) ~= typeof(x). Would the best solution be to hard-code every type into a table, then?
Many thanks.

Yes, hardcoding things would be safer.

If we do add a new type, thanks to Robloxā€™ focus on backwards compatibility itā€™s very unlikely that not handling that new type temporarily would break your game. When you do update your game to use some new feature that uses the new type, then you can also update your serialization code concurrently.

1 Like

Yes, we still do not provide Vector3 and similar type serialization to Data Stores.
If thatā€™s a feature that will be useful for you and other developers, you can create an engine feature request topic.

To answer the other part of your question, tostring result is the same as before: X, Y, Z

And we do not currently have plans for more changes like this to other types, but we continue to work on performance improvements for them.

1 Like

This is a perfect illustration that you should not blindly use performance as a guide for writing the code :slight_smile: Even if this difference exists, it will probably not exist forever and itā€™s actually plausible that at some point 3-arg ctor is going to be meaningfully faster.

As a rule of thumb, type reflects underlying VM types, so if we add more underlying VM types (e.g. records), thereā€™s going to be values that return something else. Itā€™s not backwards compatible, but when presented with an option to preserve backwards compatibility here even though itā€™s a lie and obscures semantical details (e.g. ref equality), weā€™d rather not lie. Thereā€™s no specific plans for other changes in this area yet but something is likely to happen in a year time span.

No existing plans atm but in general I would not recommend relying on weak tables for atomic (builtin Roblox) types too much. If we decide to change Vector2 or UDim or some type like that to be a value type, these could break as well. Larger types like CFrame are unlikely to follow suit.

No current plans. Thereā€™s various ways to increase simulation/rendering precision at a distance from origin; itā€™s not clear that we necessary would use doubles to solve this problem. Thereā€™s also storage and performance impact of using double vectors that weā€™d need to carefully evaluate - overall this will definitely not happen in the next year or two.

6 Likes

I just wanted to add that I think this is the correct change because I have an isheap function that still works with this change:

local function isheap(v)
	local t=type(v)
	return t=='userdata'or t=='table'or t=='thread'or t=='function'
end

(I use this function to test for what I can have an __gc callback on)

1 Like

Will Vector3s be added as constants in the VM?
If so, will it support constant folding? e.g. Vector3.new(1, 2, 3) * 2

Would it be feasible to allocate 4 registers when using CFrames in fully type-annotated code? It might need 5 so it can keep a pointer to the original userdata or create one lazily.
If not, would using 4 Vector3s to do matrix transformations yield better performance?

3 Likes

This has indeed been fixed last week, I forgot to notify here :slight_smile: Please let us know if you see any other odd performance issues.

This is currently impossible for us to do safely because getfenv exists.

These types of optimizations might be possible in the future but currently itā€™s a rather far future :slight_smile: Iā€™d say we have a year of work ahead of us before we can start contemplating using types in this fashion.

Itā€™s a bit hard to say, Iā€™d recommend profiling this; thereā€™s many variables involved here. Plus keep in mind that while CFrame is going to stay heap allocated for a while, we do have some further improvements planned here so performance seen today for e.g. CFrame * CFrame is likely to improve in the future.

And of course CFrame * Vector3 doesnā€™t suffer from this problem, itā€™s only an issue when youā€™re creating new CFrame objects rapidly.

2 Likes

This is actually an important point because this means that when you perform a series of transformations, you should transform vectors instead of concatenating transforms.

E.g. instead of

local v = translate * rotate * scale * position

You should do

local v = translate * (rotate * (scale * position)))

(which is beneficial even when CFrames arenā€™t heap allocated, but is especially beneficial in Luau)

When a concatenated transform is reused for just a few vector transform operations, itā€™s likely faster to still do the transform on vectors repeatedly; itā€™s only when a compound transform is used for dozens of transformations would you see the benefit. E.g.

local trs = translate * rotate * scale

-- proceed to use trs to transform 100 points
3 Likes

For the simplest constant case (e.g. Vector3.new(1, 2, 3)), would there be any benefit to loading a constant until getfenv is used?

It might be useful if something like this could be done for simple constants of non-native types. For example:

-- Create constants every time the code runs.
-- Better readability.
local function foo(cframe: CFrame)
    local part = Instance.new("Part")
    part.Anchored = true
    part.Size = Vector3.new(1, 1, 1)
    part.Color3 = Color3.fromRGB(0, 191, 255)
    part.CFrame = cframe * CFrame.new(0, 1, 0)
    return part
end

-- Create constants once and store them.
-- Has better performance, although part creation is the real bottleneck here.
-- Worse readability, especially for large functions with many constants.
local size = Vector3.new(1, 1, 1)
local color = Color3.fromRGB(0, 191, 255)
local transformation = CFrame.new(0, 1, 0)
local function foo(cframe: CFrame)
    local part = Instance.new("Part")
    part.Anchored = true
    part.Size = size
    part.Color3 = color
    part.CFrame = cframe * transformation
    return part
end

The VM could allocate these constants as needed and keep a weak reference to them, perhaps like strings. My game has a compile process now so I donā€™t really need to worry about this anymore, but readability vs performance for userdata creation was a big dilemma for me back in the 2015 era.

Thatā€™s more than I could hope to hear. These VM improvements help keep the platform competitive and exciting to develop on. The recent table.remove/table.insert optimizations made my gameā€™s pre-publish compile process 2x faster.

1 Like

Yeah this is possible in theory, and we sort of do something like that in some cases that this margin is too narrow to contain, but if you extend it to constant folding the mechanisms we use for this right now arenā€™t truly sufficient. Thereā€™s also some consideration for extensibility of a mechanism like this.

So far we havenā€™t identified this as a significant source of issues for any benchmarks we work with; if you want to contribute an instance creation benchmark (something realistic ideally that includes various types and constructs a hierarchy), we could look into what performance opportunities it presents - most of our performance work starts and ends with concrete code that we want to make faster, as it allows us to focus on the right elements of the stack.

1 Like

This is a very great thank you Roblox! :+1:

Howeverā€¦

Was this necessary. Why canā€™t type() just return ā€œuserdataā€ for backwards compatibility? I mean it isnā€™t userdata but changing this like this isnā€™t in my opinion very great.

Vector3s with NaN components should either error when using them as the key in assignments (like when using NaN as a key in assignments), or there should be a special case for NaN components (for compatibility)

Fix for this issue has been released, vector keys with NaN components are not allowed just like NaN numbers.

1 Like

We have released a fix for the vector hash function, your issue with vector table key slowdown should be fixed.

The core issue is that after the update, Vector3 will not be userdata and will behave differently compared to actual userdata types - see notes about rawequal and table key behavior, including GC.

So we have two options:

  • Lie about the type of Vector3 values in service of backwards compatibility. This will help in some cases, but wonā€™t help in all cases. Additionally this will result in a permanent lie which would make it so that you canā€™t really rely on type to tell if something is a userdata or not

  • Make it so that type correctly communicates the underlying value type, with some risk of breaking existing scripts that rely on type instead of typeof

Right now based on the information we have today plus discussion in https://devforum.roblox.com/t/does-anyone-still-use-type-vector3/686757 itā€™s not clear that we need to lie - and if this affects a small number of games, weā€™d rather have developers of these games update them.

Weā€™ll post a separate announcement focusing on this part of the change.

1 Like

Any reason why itā€™s ā€œvectorā€ and not just ā€œVector3ā€? Would be nice if it could be both a consistent and forward compatible name.

Ahh, this seems like a technical roadblock that prevents the vector type from being mutable.

I assume this is to how Instances handle property changes, where they only update if itā€™s to a property of that instance, so changing Part.Position.X wouldnā€™t actually change the Instance.

1 Like

This got me excited for a second.

I was working on a UI-based tower defense game, and I stumbled upon some horrendous performance issues when indexing the Absolute properties. Really hope you guys can address that in the coming weeks, little unfortunate how this doesnā€™t focus on Vector2 types at the moment as well :frowning:

1 Like

This has been enabled in Studio as of June 10, so the beta is no more (the change is always active in Studio). We are planning to enable it on live clients and servers on June 14th.

7 Likes