Best ways to improve LN performance

Hi!

This topic can be a guide for people that want to create or improve the logic nodes performance.

As i know, switch case performance is faster than ifs and it keeps the things more readable. But is there any situation when if is the better option? If we have only two cases in the switch, it keeps the best way? If we have only two cases in the switch but there is too many other objects using the same node, this would not be better?

Also there is another way to booleans i see in some nodes that is:

bool ? true : false;

What is the name of this method (like switch)? I can’t see any reference to it in the Haxe API. And does this method is faster in some way?

If someone have a question or suggestion to improve our practices i would like to know :slight_smile:

One more question: in Sleep node the time is counted in a different way than On Timer node. The way On Timer node does it seems more confortable for me to get other values. For example, i have added a Progress output in the On Timer that goes from 0 to 1, but i cannot do the same for Sleep node.

The reason why i need to get the progress of the sleep node, is for example to interpolate some action that happens while the sleeping is happening.

Would be a good idea (for performance and usability) to change how Sleep node works?

I think this heavily depends on the target, some targets might not even support switch statements so the code will look different. In general you can say that switch statements are faster for many comparisons as there usually is a lookup (via hashing for example) so instead of comparing the values one by one there is a jump position thats computed. So instead of a O(n) complexity (n steps required for n values) there is a O(1) complexity. But, for two values for example there is probably no difference (O(1) vs. O(2)), it could even be slower due to overhead, I don’t know. In general, use if for few values and switch for more values (it also helps with readability but is noise when using just two cases).

This is called the ternary conditional operator (ternary because it has three operands). I don’t know how its handled in Haxe and this might also heavily depend on the target language. I don’t think there is a big difference in performance and if there is, its probably negligible.
However there is one big exception and that’s shaders. In shaders, branching (taking multiple different paths, if, for) takes a lot of performance away because the pixels are calculated in sync and because of that all outcomes have to be calculated even if they are discarded later. In glsl, the ternary operator is actually a lot faster because it doesn’t result in branching when not used for complex situations or calling functions etc.

So, use the ternary operator when it supports the readability of the code or if you write glsl.

I think what takes the most time for logic nodes is the overhead due to the function calls between the nodes and all the (implicit or explicit) casting when passing the arguments. In general: don’t optimize without testing (its difficult to predict performance) and if it runs fast then don’t optimize more than required. Readability of code is also important as long as it isn’t slow.

Both nodes probably do more or less the same. Maybe its time to combine the Sleep node with the Timer (not On Timer) node? It also has a progress output. Performancewise I don’t know the difference between the nodes.

2 Likes

Testing switch and if, i can’t see any performance difference (the ms is not stable, that is why switch is with higher ms). I think we would use if to avoid the overhead and because it is supported by all targets.

Choose whatever looks best :slight_smile: Maybe the target doesn’t even support switch statements or Haxe just converts them to if/else anyway.

Btw, in such cases the overhead (if it exists) just plays a role for small input sizes/few comparisons/etc. An algorithm with a lower complexity class will always perform better regardless of constant factors or overhead starting from a certain input size and onwards. But for small input sizes they might be slower or it just doesn’t matter at all. (It’s a bit theoretical and mathematical, if you’re really interested have a look here)

1 Like

I am adding On Local Axis checkboxes to nodes that miss this, like Apply Torque, Apply Torque Impulse, Get RB Velocity, etc. but i realized that the script was getting too big (specially in Get RB Velocity). What is the best way to go? Leave it as it is now (some with On Local others not), none with On Local or add to all?

A big file is not necessarily bad. If they are repeating patterns, you can add a function to handle common stuff (make sure to inline it if possible).

1 Like

I will try to add functions as getLocalLinearVelocity, but i am not sure if i can yet. I will try it but i still very slow in coding. Where can i find the function for body.getLinearVelocity and others (not physics only)?

public function getLinearVelocity(): Vec4 {
	var v = body.getLinearVelocity();
	return new Vec4(v.x(), v.y(), v.z());
}

In the RigidBody.hx i found this, but for me this does not look as a “complete” function.

About the inline i will search later but i already found a haxe documentation for this.

body is a bullet.Bt.RigidBody as you can see at the top of the class.This type looks like an extern and can probably found somewhere here: https://github.com/armory3d/haxebullet. Can’t you just call the RigidBody.hx function? All it does is to copy the vector returned by body.getLinearVelocity() into a new Vec4.

Edit: found it. If you search for m_linearVelocity in that file you can see how it is set.

1 Like

Many thanks!

I just get scared with btRigidBody.h and probably the best way is to just call the RigidBody.hx function. :smiley:

I still don’t know where exactly i should create the function, i think i should create it in iron/object/Transform, and call it in the nodes that requires On Local Axis (at least for now). All i can do is mimic how the existent nodes call the functions. Then, i had created an inline function in iron/object/Transform just to see how it works, and in the node, i import iron.object.Object and iron.math.Vec4. The result i have is that my function is an unknown identifier in the node. Am i missing something?

Also i am trying to test if the function getLocalVecFromWorld does the same work, but it also is returning unknown identifier even with using iron.object.TranformExtension in the top.

If you may help me to understand it when you have time, here is the full script:

package armory.logicnode;

import iron.object.Object;
import iron.math.Vec4;

using iron.object.TranformExtension;

class VectorToObjectOrientationNode extends LogicNode {

	public function new(tree: LogicTree) {
		super(tree);
	}

	override function get(from: Int): Dynamic {

		var object: Object = inputs[0].get();
		var vector: Vec4 = inputs[1].get();

		if (object == null || vector == null) return null;

		return getLocalVecFromWorld(object.transform, vector);
	}

}

Functions from static extensions in Haxe are not called like regular functions but instead on objects itself. The object the function is called on is implicitly passed as the first parameter.

So instead of writing

return getLocalVecFromWorld(object.transform, vector);

You need to write

return object.transform.getLocalVecFromWorld(vector);

That’s where the name static extension comes from. You extend an already existing class with other additional functionality in class where the functions are static and take the actual object as the first parameter.

If you really want to call the function as in the first example, you must not use the using keyword and instead import is as a regular class.

1 Like

This method is bit different. If i try to do the same as it, using vector subtract and the Vector To Object Orientation, the same happens.

The vector returned by getLocalVecFromWorld seems broken when the relative object is not looking at world Y:

Captura de tela de 2020-11-01 13-13-31
(should be 0, 2, 2 — if the object is looking at Y the value is exact)

Is this some internal issue with the vec4 math or transforms?

Hi @knowledgenude

I am sorry but I did not fully comprehend what you wanted to achieve here.

According to my understanding, you wanted to show the object’s linear velocity in its local axis. This can be simply done by multiplying the local object rotation vector with the global linear velocity. This has nothing to do with Bullet in particular.

I have some experience in working with Bullet, so I might be able to help.

Best regards,
QC

1 Like

Hi @QuantumCoderQC ,

I am trying to find a function that do this, because it looks like a common stuff (many nodes have On Local Axis checkbox). In TransformsExtension.hx there is two similar functions getLocalVecFromWorld and getWorldVecFromLocal, maybe i should create another function there to call in this nodes? If i do this, i only will call this function in the Get Velocity and others nodes, because i think it is most common getting the velocity in the object orientation instead of in world orientation.

The problem is that i am poor in coding, looking in how the other functions are structured, i don’t understand them, like here localVec.clone().applymat4(t.worldUnpack), i am blind without know from where is coming this functions.

I know that getLocalVecFromWorld do what i want, but it do an extra step that is consider in the math the location instead of just the orientation.

I’ve put this function in TransformsExtension.hx but i don’t know how to multiply it without making assignments to inline the function:

public static inline function worldVecToOrientation(t: Transform, worldVec: Vec4): Vec4 {
		return t.world.right().add(t.world.look()).add(t.world.up());
	}

If it’s just this multiplication, you can easily write you’re own function for this. Just use the code you’ve written anyway and convert the local variables the code uses into function parameters.

On the other hand: the getWorldVecFromLocal and alike functions in TransformExtensions can indeed help you here (I’m also not really understanding what you want to do): they transform coordinates given in one coordinate space into another. If I understand you correctly, what you want to achieve is such a transformation, but without translation (maybe even without scale, I don’t know which way it makes more sense). I will come back to that translation thing later.

I think you’re just accidentally confusing from which space to convert (you’ve written above that you’re using getLocalVecFromWorld). If you have a checkbox On Local Axis, you want that the user inputs a vector relative to the objects orientation that is converted into world coordinates so that the physics engine can work with it (if that’s how Bullet works), not the other way around. So that (1, 0, 0) as an input means 1 step on the x axis of the object, not of the world space (thus, you need to convert it from object to world space).

However, @QuantumCoderQC’s method is probably still faster because you don’t need to apply a matrix, you can just multiply a few axes.

If you want to use the TransformExtension functions: there is one important thing to keep in mind when applying transformations onto vectors. Vectors can represent a direction or a location, which is usually derived from the context. But, if you have Vec4s (that’s why they’re used so often), you can actually tell the computer how to interpret that vector. If the last (w) coordinate is a 1, that means that the vector is a location, if it is 0, it is a direction. This works because of the way matrix multiplication works – transformation matrices are usually 4x4 (or 4x3) matrices where the first three colums are the rotation and scale and the fourth colum is a translation. If the w component of the vector is a 0, the fourth column does get multiplied by 0. This video explains this very well, apart from matrix multiplication you don’t need to know much to follow along.

Long story short (was a bit mathematical, sorry): Make sure that the w component of the vector is a 0 in your case as you’re handling directions.

First of all: learning by doing is the best you can do (:+1:) , but try to find tutorials, even if they are written for other languages (Haxe tutorials are rare). The principles are the same for the most common languages and you will benefit a lot from it. Knowing what objects and classes are and how functions work is absolutely essential in order to really understand what’s happening. The most important thing is: never give up! It’s difficult at the beginning but it gets easier over time. And don’t try to understand everything at once, for me e.g. it works best to learn a bit about something and then get back to it after a while (might be months in between).

A quick explanation of the code you’ve mentioned:

localVec.clone().applymat4(t.worldUnpack)

You have a vector, called localVec of a type of one of the vector classes. They provide methods/functions to work with objects of those classes. For example there is a clone() function that copies the vector into another object to allow you modifiying one object without changing the other (search for mutable/immutable types to understand why this is needed).

There are two types of functions: those which return a value or those which don’t (Void return type). The value (might be an object) that is returned is then used at the same place where the call occured (you can think of replacing the call with the returned value in the code, it doesn’t work like that (it doesn’t replace the code), but that might help you to visualize what’s happening).

So instead of localVec.clone() think of theVectorThatWasReturnedByClone. In the code example, that would evaluate to:

theVectorThatWasReturnedByClone.applymat4(t.worldUnpack)

Now you will probably understand how it works: you can chain functions together and every function will be called on the object the previous function returned. So to find out where applymat4 is defined, you have to look up the return type of clone(), which is another vector.

2 Likes

I’ve opened a pull request here https://github.com/armory3d/armory/pull/1981 so you may feel free to review it and tell me how i can improve it.

If the PR may be improved, i will do it tomorrow with the information you send, because i am a bit tired now.

I think i’ve learned a lot today (it is in fact almost nothing, but i was really hard for me). I will improve it with the time :slight_smile:

About the immutable and mutable vectors, i don’t know exactly the logic behind this, i really need to take a deeper look on the functions you mentioned here, including the clone().

I really appreciate your help guys, many thanks!

2 Likes