Zuzu-Typ / PyGLM

Fast OpenGL Mathematics (GLM) for Python
zlib License
214 stars 29 forks source link

in-place add operator += on vec3 modifying RHS of prior operator +. #230

Closed kwan0xfff closed 8 months ago

kwan0xfff commented 8 months ago

I am seeing a side-effect involving two operations. I've repeated this in two different environments: Env 1:

Env 2:

Sample code:

#!/usr/bin/env python3
"Test of in-place add operator += on vec3"

import glm

v0 = glm.vec3(0,0,0)
v1 = glm.vec3(1,0,0)

vx = v0
print ("v0 check 1", v0)
print ("v1 check 1", v1)
vx += v1
print ("v0 check 2", v0)
print ("v1 check 2", v1)

print ("vx        ", vx)

Output:

$ ./vec_iadd.py 
v0 check 1 vec3(            0,            0,            0 )
v1 check 1 vec3(            1,            0,            0 )
v0 check 2 vec3(            1,            0,            0 )
v1 check 2 vec3(            1,            0,            0 )
vx         vec3(            1,            0,            0 )

The variable v0 has an initial value of vec3(0,0,0) but is modified to vec3(1,0,0) after the += operation. Value v0 was okay after the '+', but was somehow modified after += in which it was not involved.

FYI, I have not tried this against GLM to see what behavior it gives.

Zuzu-Typ commented 8 months ago

Hi @kwan0xfff ,

the behavior you've experienced is a result of glm.vec3 being an object, rather than a primitive type.

Primitive types in Python include int, bool, str, etc. If you assign a variable with a primitive value to another variable, you effectively copy the value. Objects can be quite big, taking up a lot of memory. Like a list for example. It can hold thousands of values. It would be very expensive to always create a copy of it when you assign the list to a new variable. Instead, a reference to the memory address that the list is stored at is put into the variable. This is all abstracted away of course, but what this means is, that both the old variable and the new variable point the same memory address and thus the same list.

You can see the same behavior as you've seen with vec3:

mylist = [1, 2, 3]
mylist2 = mylist
print(mylist)
print(mylist2)

mylist2[0] = 5

print(mylist)
print(mylist2)

If you want to circumvent this issue, you need to create a true copy of the vec3. There are a couple of ways to do this. The easiest being:

v1 = glm.vec3(v0)
kwan0xfff commented 8 months ago

I see what you're saying... vx = v0 results in id(vx) == id(v0); in effect, the object's location is copied across from v0 to 'vx`.

Alas, no way to make vec2 or vec3 behave like complex numbers, which seem to be primitives. Thanks.