Revised thoughts on Squirrel Math

Been thinking a bunch about the vector math classes mentioned in the previous post (vec2, vec3, etc). I was ready to try proposing “something” to let you add .anything to a class to access members hidden in (say) userdata. As it turns out, that already works.

The _get and _set metamethods talk about indexes in the documentation, so I mistakenly assumed they were for creating array-like syntax (maybe they are too), but in actuality they do exactly what I want: let you handle .anything

This is, of course, a Squirrel implementation.

A better use would be to write these metamethods in native code. Also instead of a class, use a UserData type to hold the true data (vector parts, a matrix, a quaternion), and attach all functionality using a delegate.

Using native has the added advantage when it comes to the operators (metamethods). Types can be checked far quicker this way (as constant values like OT_FLOAT), so my concerns about wasted time doing checks for each type shouldn’t be much of a problem anymore. I have to give somewhere, and the flexibility Squirrel provides is worthwhile.

Creating instances though is the question.

We could have a delegate called “vec2_delegate”, and a native function called “vec2” (like how JavaScript classes work). Have the function push the UserData structure on to the stack equal to the values passed, then attach the vec2_delegate to it. The delegate has a _typeof method that returns “vec2”. Finally, so long as it’s the only new thing on the stack, the vec2 function say it returned a value, and thus will be assigned by reference to

Copy Constructor implementation will be native, detected the same way as before, but no longer a cloning issue as it’ll be native code.

Just a few things to figure out:

1. How to “throw null” natively (required by _get and _set).

2. How to differentiate between UserData types (vec2, vec3, mat4, etc).

3. How to create and popluate Userdata.

Unrelated, but sq_getmemberhandle(v,idx,&handle) looks useful for optimizing data read/writes between the VM and Native code. sq_getbyhandle and sq_setbyhandle. The SQMEMBERHANDLE type however only contains a bool (_static) and an index (_index), so I’m not entirely sure how we get quick-access to data yet. It looks like you may push the value you want to set (sq_pushint, sq_pushstring, etc) and then follow it with a call to sq_setbyhandle. Reading the information though, it sounds like Member Handles may be a class-only feature, so this may not be what I’m looking for.

Classes can have UserData associated with them! sq_setclassudsize sets the size of the UserData attached to a class. sq_setinstanceup and sq_getinstanceup are a pair of functions for manipulating a UserPointer associated with a class instance (not UserData). That said, calling sq_setclassudsize will automatically set the internal classes UserPointer to the location of allocated data. Following up with a call to sq_getinstanceup will tell you where to put your data.

What’s missing?

The only thing missing is a nice way of handling Floats with new Vector and Matrix types (as userdata).

One option is to have a “scalar” or “real” type that exists for doing math with Vectors and Matrices. MyVec.x *= 10 is going to work fine already, but MyVec *= 10 will not. I could put in similar code as before, a check “if ( vs.type == OT_FLOAT )” then treat it as a scalar, but that doesn’t handle the front case (MyVec = 10 * MyVec). That’s why I’m suggesting a .toscalar() or .toreal() function. toscalar() I believe makes the most sense, as the operations being performed are scalar math ones. In addition, the float can have a .tovec3() or similar to create boring (1,0,0) type conversions.

Vectors will already support using any float as arguments “vec3(0,MyFloat,12)”. So classes like 2×2 or 4×4 matrix should support constructing with equivalent vectors “mat2(vec2(1,0),vec2(0,1))”. If feeling very adventurous, take any combination of float and vector types.

Yeah, the only hold-out is the “NewVec = 10 * OldVec” case.

I don’t really want to disturb the standard Float type by introducing extra check “is the previous variable a Matrix?”. Requiring conversion via .toscalar() may not be unreasonable after all, even though all operations like magnitude and normalize are available inside the Float (well, my modified float anyway).