Behavior change: type() will start returning 'vector' for Vector3

UPDATE This has been enabled in Studio as of June 10. We are planning to enable it on live clients and servers on June 14th.

As many of you are aware, we’re working towards making Vector3 a native type in Luau VM. This is motivated by the importance of Vector3 type to code that runs on Roblox and dramatic performance improvements this can bring.

This is already highlighted in the beta post, but to raise awareness we’re making a separate post about a particular part of this change in case this was missed, as it’s a comparatively riskier part of the update:

type() will start returning “vector” for Vector3 inputs, instead of the currently returned “userdata” string

This change will happen over the next few weeks (we’re likely to start doing the rollout next week but it might take a couple weeks to deploy fully). If you have games that rely on type() to check if a value is Vector3, please change them to use typeof() - typeof() will continue to return “Vector3” for Vector3 values as it used to.

If you have any questions for the general Vector3 update, please make them on the thread about the beta instead of this one: Native Luau Vector3 Beta.

Why not continue returning “userdata” from type()?

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

Given that we can’t keep Vector3 a userdata, we only 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

Based on the information we have today, we believe that changing the behavior of type() to correctly communicate the underlying type will not result in significant disruption and will be much easier for us long term. Unfortunately, we can’t reliably find all games that may be comparing type() to userdata when handling Vector3 values (this usually works by inspecting the value for various members such as Magnitude via a pcall); hence, this announcement.

Note that we recommend using typeof to distinguish between various Roblox types over type in all cases, as it’s going to give you correct information and will do this much faster than the alternative type+pcall approach.

138 Likes

This topic was automatically opened after 19 minutes.

Is something similar like this going to happen to Vector2?

24 Likes

Probably not considering Gui code isn’t as resource intensive and doesn’t require the same level of optimization as manipulating 3d space, especially when done for custom physics simulation, camera movement, internal use in rendering, and a slew of other things in scripts like doing distance checks, dot checks, and probably way more than I don’t even know about.

Edit :

Changed my mind on this, if it isn’t too much effort for Roblox engineers they may as well apply the same optimization considering most of the API looks the same for both value types.

7 Likes

Vector2s are used for much more than just GUI code. I’d like to see something like this optimization occur for it as well.

20 Likes

I agree, I use vector2 quite a lot, and seeing this optimization would be greatly appreciated

5 Likes

How are you able to inspect values for various members across games without also being able to retroactively add new members with different default values on past/future games?

6 Likes

From the original post

Looks like this will happen eventually.

10 Likes

Would love to see this extended to other types, especially Vector2s and CFrames. My current project uses Vector2s extensively for unit positions to save up on network and system memory (it’s an RTS).

4 Likes

How does this effect calling type() on a Vector2?

Couldn’t it be argued that Vector2 and Vector3, despite both being Vectors, are different types? (Maybe I’m overthinking this due to a misunderstanding of Luau)

3 Likes

The only type that will be effected currently is Vector3 (According to @kingerman88’s reply Vector2s/CFrames will receive different optimizations and such, and, Vector2 still returns userdata in the beta)

5 Likes

Currently this has no effect on Vector2 values. Our general recommendation is to use typeof to distinguish Roblox types though; typeof returns “Vector2” for Vector2s and “Vector3” for Vector3s, which will not change.

7 Likes

How come there’s two type functions? I’ve always been confused which one to use

3 Likes

type is the native Lua type function, I believe.
typeof is a custom function implemented by Roblox which returns more specific type info (so for example, typeof(Rect) would return Rect or something whereas type would only return userdata).

5 Likes

How will native vector support affect how much memory a single Vector3 occupies? I assume Vector3 values have some memory overhead to them, so it would be more efficient to store 3 numbers rather than storing one Vector3, right?

[Edit]: I just tested this, and came to an astonishing conclusion:
I ran the following piece of code, which measures how much memory is used to store 30000 numbers from two different methods. In one, I store 30000 entries straight in a table; in the other, I store 10000 entries of Vector3s, which hold 3 numbers each. In theory, the minimum memory occupied by this test should be 8 bytes per number * 30000 = 240000 bytes or 240 kilobytes.

When I first ran the code on the old Vector3s (beta disabled), I got 608 KB for the vector3s and 512 KB for the numbers. Then, when I enabled the beta and ran the test again, I got 256 KB for the vector3s and 512 KB for the numbers. This seems to indicate to me that Vector3s are now super efficient for storing number data.

I tried out the test with different quantities, which resulted in Vector3s always being much better than numbers. Strangely, I kept encountering very nice looking numbers like 4096 or 8192 or 1024. It is a bit concerning, because the perfectness of these numbers suggests something is incorrect about the way I measured the results. Can someone try this test and confirm my results?

local n = 10000
do
local start = collectgarbage(“count”)
local s = {}
for i = 1, n * 3 do
s[i] = math.random(-1000, 1000)
end
print(collectgarbage(“count”) - start)
end

wait(10)

do
local start = collectgarbage(“count”)
local s = {}
for i = 1, n do
s[i] = Vector3.new(math.random(-1000, 1000), math.random(-1000, 1000), math.random(-1000, 1000))
end
print(collectgarbage(“count”) - start)
end

[edit #2]:
Vector3int16 does not appear to be native-ized, and it is only marginally better in terms of memory used than the old non-native Vector3s. Very surprising.

6 Likes

A Vector3 conceptually would be stored using only 3 numbers in an internal structure so in terms of memory usage, I would assume that there wouldn’t be much of a difference unless there was some other internal memory optimizations related to how Lua works itself, or they are doing some structure packing that wasn’t done before.

The main important change is that Vector3 was a heap-allocated object, which means everytime you created one the memory had to be allocated dynamically, which has a decent performance overhead compared to non-heap objects, especially in tight loops. With the new update, a Vector3 will be a value type like a number or boolean. For example, you don’t expect a number to be dynamically memory allocated, its just a value. It doesn’t have dynamic size. A Vector3 will now be just like storing 3 numbers. Though, I expect that there is some specific optimization cases for the new Vector3 and you should use it instead of using 3 separate numbers.

Those are some good results in your test though. I would suggest doing some speed tests as well and see what the differences are.

3 Likes

It turns out that Vector3s actually lose precision over just plain numbers, so it does explain why even on the old Vector3 system, you could see the Vector3s occupying less memory than the numbers counterpart by like 25% or so (depending on what quantity of values you test).

But when I compare the old Vector3s to the new Vector3s, the new Vector3s are always using significantly less memory than the old ones, and the precision appears to be the same.

I am aware of the major performance improvements and will definitely be getting huge leaps in performance from it, but I also store a lot of data in memory (250 MB of it, actually, in script memory), so this will also help with that.

4 Likes

That’s interesting. Must be some sort of hashing-like optimization that can result in collisions sometimes. Maybe you only have to store the length and direction of the vector, and the components can be determined from that (with minor precision loss).

That’s good to hear. I don’t store that much Vector3s in memory for an extended time, so I’m mostly excited about the performance improvement, but thats good to know in the future if I do end up storing them.

5 Likes

Yes, that is the case. In fact, all value types take the same amount of space to store, so a Vector3 and a number take up the exact same amount of space now.

However, you have to be careful here: Yes, storing your number data in Vector3s is now “super efficient”, but only space wise. If you actually store your numbers like this, you’re also going to have to write code that gets and sets them at some point in order to use them, and that code will become significantly less efficient performance wise, because it has to take apart Vector3s and put them back together again, on top of doing a non-trivial calculation to find the index in the table.

So, you’d have to have a pretty unique scenario where you’re storing a ton of temporary data, and only accessing a very small portion of it to get a benefit from packing number arrays into Vector3s.

5 Likes

Luckily I hit that exact use case! I have 250+ MB of map data that is loaded up into a gigantic table on runtime, which is then read throughout gameplay for access to collision data. The data itself represents 3D points so Vector3 is its natural state, but previously I had broken them down into components for both storage and later calculations b/c old Vector3 being bad.

Let’s just say this is a huge life saver in memory efficiency, speed (which is important at the scale of calculation I do every frame), and convenience of not having to read all of my equations with a gazillion xyz variables. TYSM!

3 Likes