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.
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.
This is a perfect illustration that you should not blindly use performance as a guide for writing the code 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.
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)
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?
This has indeed been fixed last week, I forgot to notify here 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 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.
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
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.
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.
This is a very great thank you Roblox!
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.
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 ontype
instead oftypeof
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.
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.
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
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.