-
-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Immutable vectors #152
Comments
I think LibGDX sacrifices safety for conciseness and performance - the difference might not be noticeable on desktop, but it does somewhat matter on mobile. That being said, immutable vectors and colors would be a great addition and might prevent some non-obvious bugs - but since there is no @jcornaz Would you like to try implementing such immutable classes or is it a feature request? |
Yes, it does. However as you said it is less true on desktop. And I think it may even actually lead to worse performances, as it is dangerous to share the mutable vectors mutable different threads. Making performances improvement through multi-threading almost impossible.
This is a feature request. But I don't mind trying to implement it myself and propose a PR. |
I'd be happy to review the PR, thanks. |
What would you think about the following design: data class ImVector2(val x: Float = 0f, val y: Float = 0f) {
val isZero get() = x == 0f && y == 0f
// immutability allow some performance improvements as well, by making able to cache results:
val len2 by lazy { (x * x) + (y * y) }
val len by lazy { sqrt(len2) }
// [...] Other relevant members
companion object {
val ZERO = ImVector2()
val X = ImVector2(x = 1f)
val Y = ImVector2(y = 1f)
}
}
// Conversion from/to LibGDX vectors:
fun ImVector2.mutable(): Vector2 = Vector2(x, y)
fun Vector2.immutable(): ImVector2 = ImVector2(x, y)
// Operators as extension functions
operator fun ImVector2.plus(vector: ImVector2): ImVector2 =
ImVector2(x = x + vector.x, y = y + vector.y)
fun ImVector2.lerp(target: ImVector2, alpha: Float): ImVector2 {
val invAlpha = 1f - alpha
return ImVector2(
x = (x * invAlpha) + (target.x * alpha),
y = (y * invAlpha) + (target.y * alpha)
)
}
// [...] Other operators written as extension functions I'am only talking about design. Of course I'll make more operators, and I'll do it for Vector3 as well. Colors may come in a separate PR. |
Not sure about the name of the class. We could either go with a verbose Lazy fields might be too much of an overhead - instead of just 2 float fields, each vector object would hold a ton of float wrappers if we go that way. Most available vector operations just aren't heavy enough to justify the cache. Let's trust the user to save the computation results. Some extra dynamic properties (like the length) are OK, but I think the API should have all of the original methods from Kotlin built-in conversion methods usually start with |
I was not sure either. I like
I don't want to add everything in lazy properties. Only the expensive ones, like
Yes everything available from LibGDX vectors should be available. But is it ok if is it available as extension functions? Or would you put it in the class?
Yes. But in this case it should be named
I think |
Each lazy property is an extra object created for each vector, regardless of whether you use that particular expensive method or not. Caching in this case might be a premature optimization of a problem that won't affect most use cases. Feel free to write benchmarks, but I think the memory cost is just not worth it - IMHO, vectors should be as close to value classes as JVM (currently) allows.
Either way works. I think keeping them in the class might be more readable.
I feel like |
Actually, does it make sense to have immutable vectors without having immutable matrix and quaternions? I'm starting to think that adding everything would be too much. And maybe this feature request is too much against the concept: "make LibGDX as Kotlin-friendly as possible without turning the API upside down" Although I'd love to have immutable matrix, vectors and quaternions. |
Immutability is encouraged in Kotlin, as you've said. As long as using the immutable variants is optional and doesn't get in the way, I don't see why we wouldn't add them. I'd only suggest to use delegation for complex methods rather than try to reimplement every single Vector/Matrix operation. It's OK to create a mutable instance, perform the operation on it, and convert it into the immutable variant. |
Start with just the vectors, though. I try to write tests for every feature, and I imagine the vector operations alone would require quite a bit of tests. It's also easier to thoroughly review smaller PRs. |
Should we provide operators overloads accepting LibGDX For instance on could do PROS:
CONS:
|
In times like these, I wish Kotlin had union/structural types. I could see how that might be very convenient, I'd say we can stick to immutable vector operations and explicit conversions when interacting with mutable variants - at least for now. Unless you feel up to implementing the extra methods. |
@jcornaz Any updates on this? If you started working on this and have some code that we can use to speed up the implementation, feel free to setup a PR. |
Yes I started it. I want to add some tests before creating the PR. I'll see if I have time to propose something this weekend |
I'm basing my tests on the fact that "all my functions should return same value LibGDX However LibGDX returns quite strange results for angles. // angleRad without argument returns angle from x axis.
println(Vector2(0.0f, 1f).angleRad()) // 1.5707964
// but if one explicitly specify X axis:
println(Vector2(0.0f, 1f).angleRad(Vector2.X)) // -1.5707964 What am I supposed to do here? Should I replicate this inconsistent behavior for the sake of behaving as LibDGX does? |
@jcornaz It does seem like a bug. Try adding an issue to the official LibGDX repo. Although both approaches are a source of potential bugs, I think we should choose correctness over consistency in this case and wait for LibGDX team to fix their implementation. |
I open an issue for it: libgdx/libgdx#5385 |
It's not only inconsistent. It is incorrect as well. |
Just to clarify, I meant consistency with LibGDX. Reimplementing the bug would be consistent with LibGDX API (and incorrect), but in this case we should sacrifice full LibGDX compatibility and go with the correct solution. |
This one is quite an edge case case, and is only about consistency: println(Vector2(-1f, -0f).angleRad()) // -3.1415927
println(Vector2(-1f, -0f).angle()) // 180.0 Here the "problem" is the fact that Of course one may ask what the rationale of having Since here LibDGX's result is inconsistant but correct. Should I replicate this inconsistency? And I'd like to ask the same kind of question for |
You should definitely use methods with default params whenever possible (and sensible). Since there is no interface to implement that you have to conform to, the difference between overloaded methods and methods with default params should be barely noticeable by the users. As long as it simplifies the implementation or maintenance, go for it. As for the inconsistencies in LibGDX API: it's the KTX goal to smooth out LibGDX rough edges without completely changing the APIs. If you consider this behavior as borderline buggy, feel free to implement it the correct, consistent way. You might also want to create another bug report in their repo. Thanks. |
ImmutableVector2
ImmutableVector3
Hi,
Thank you very much for providing theses utilities for Kotlin :-)
I would like to propose to introduce some immutable classes for at least vector and colors. (could be called
KVector2
orImmutableVector2
for instance)In order to allow easy usage with LibGDX they could either:
ImmutableVector2.asVector2()
andVector2.toImmutableVector2()
Why?
Even in Java I find error-prone that classes like vector or color are mutable. It is for instance possible to change by mistake
Vector2.ZERO
, and it is a mistake really easy to do. But I think that's even more true in Kotlin where the language provide good support for immutability (val
is the default,data class
providescopy
to easily get a new object, and immutable fields can be smart-casted).Mutable vectors cannot be safely shared, and one has to do defensive copy each time it gets a vectors, which is cumbersome, unsafe, and inefficient.
In short:
The text was updated successfully, but these errors were encountered: