Welcome to TRiBot Forums

Register now to gain access to all of our features. Once registered and logged in, you will be able to contribute to this site by submitting your own content or replying to existing content. You'll be able to customize your profile, receive reputation points as a reward for submitting content, while also communicating with other members via your own private inbox, plus much more! This message will be removed once you have signed in.

TRiLeZ

Efficient Scripting

6 posts in this topic

Coding scripts in an efficient manner is very important, so please read this tutorial.

 

1. Introduction

 

There are two main areas to examine when aiming to minimize CPU consumption:

  1. The amount of calculations performed.
  2. The amount/rate of objects created and disposed.

The CPU's job is to perform calculations, but it is only able to do so many per second. Therefore, scripts should aim to carry out the least amount of calculations as possible, without affecting core functionality.

 

The amount/rate of objects created and disposed also significantly affects CPU usage. As the JVM runs, it performs garbage collections every so often. The garbage collectors work by scanning every object created, and checking if any active references to it exists. So, the more objects exist, the more CPU power will be used to check if those objects should be garbage collected. Scripts should aim to create the least amount of short-life objects as possible, without affecting core functionality.

 

2. Reducing Calculations

 

There are many ways to reduce the amount of calculations in your script. I will outline the most common mistakes scripters make which result in high CPU usage in relation to calculations performed.

 

Conditional Waiting

 

Many scripters use conditional waiting (which is better than static waiting), but the conditions are checked way too often.

 

Example: Your script has to wait for an interface to popup, then has to click the interface. 100% precise timing isn't required in this situation.

Timing.waitCondition(new Condition() {
 @Override
 public boolean active() {
  return Interfaces.get(12) != null;
 }
}, General.random(4000, 5100));

This is a common condition which scripters use to check if an interface is valid.

 

The problem with that condition is that it performs a check for the interface every 5ms (which is the default wait time between iterations). Now, does the script need 100% precise timing? Does it need to click the interface at the exact time when the interface shows up? No. A human would probably have a 100-500ms delay in clicking the interface, so why should the script click the interface with a 5ms delay? It shouldn't. You should slap a 100-300ms delay in there. Putting a 300ms delay in the active() method will make that conditional wait around 6000% more efficient.

 

Another thing to note is that if you are simply null checking an interface to check if it valid, you should just use Interfaces.isValidInterface(index) instead. That method performs a simple pull from a boolean array, then performs a simple boolean check. Interfaces.get(index) will perform that same check, with the addition of pulling an object from an array, and creating a new object to wrap around that interface object. Since that object is only being null checked, and doesn't ever get used, it will be garbage collected immediately, which gives the CPU more work.

 

So here is an improved condition:

Timing.waitCondition(new Condition() {
 @Override
 public boolean active() {
  General.sleep(100, 300); //Use a random number as an anti-ban measure. 
  return Interfaces.isValidInterface(12);
 }
}, General.random(4000, 5100)); //Use a random number for the timeout as an anti-ban measure.

Object/NPC Finding

 

I've also noticed that a lot of scripts use inefficient object/NPC finding. I'll list the best-to-worst ways of finding objects/NPCs, starting from the best.

  1. Finding by ID.
  2. Finding by tile.
  3. Finding by any integer comparison from the definition class.
  4. Finding by name.
  5. Finding by model vertex count (model.getVertexCount()) (Quite inefficient)
  6. Finding by model vertex length (model.getVerticesX().length) (Very inefficient)
  7. Finding by model point count (model.getPoints().length) (Horribly, horribly, horribly inefficient)

Avoid finding by model by all costs. I wouldn't recommend hard-coding IDs into your script, but you could find by name first, then cache the ID in a variable afterwards.

 

Looping without Waiting

 

I don't usually come across this, but sometimes I do. If you have a loop with no waits, the thread will run all of the calculations as fast as it can, and as often as it can. The thread will use 100% CPU usage if it is given access to all of the computer's cores (which may be one on old computers). Your script needs a sleep in it, and the sleep must be triggered every loop iteration, or almost every one (there are rare circumstances where sleeps aren't needed in loops, but generally, there should be a sleep).

 

Not Caching Returned Values

 

This is a big issue with many scripts. They do not cache variables which are returned by methods. If you do not cache a returned value, and call the method 5 times later within a few nano-seconds, CPU power will be wasted.

 

Example: Credits to Worthy

for (RSObject o: doors) {
 if (o != null && o.getDefinition() != null) {
  for (String s: o.getDefinition().getActions()) {
   if (s.contains("Open")) {
    door = o;
    break;
   }
  }
 }
}

This example isn't too bad, but it can be improved. getDefinition() is called twice in a row, which forces the API to query a cache twice in a row for the object definition, and creates two different objects (or not, depending on which internal cache is being used). Querying the cache twice results in a waste of CPU power. Instead, the script should cache the object returned in a variable, then use that variable.

 

Improved code:

for (RSObject o: doors) {
 final RSObjectDefinition def = o.getDefinition();
 if (def != null) {
  for (final String s: def.getActions()) {
   if (s.contains("Open")) {
    door = o;
    break;
   }
  }
 }
}

We don't need to null check 'o' because no elements in that array can be null.

 

Another reason why storing objects in variables is better is that the the returned values can be different from eachother, and one may even be null. You may have null-checked the first one, but the second, third, fourth, etc. variables could be null, and you'd get a NPE if you try to use that null object.

 

Painting

 

The more things a script paints, the more CPU power is used. This is especially the case with painting models. To paint a model, TRiBot must first calculate the screen position of all the 3D model points, then the script must iterate through all of the screen points and paint them to the screen. More CPU power is used if you are painting the convex hull of a model, since in addition to projecting the 3D model points to 2D screen points, many calculations must be performed to actually calculate the convex hull.

 

Path Finding/Can Reach

 

By nature, path finding is quite costly. Therefore, scripts should try and optimize their "can  reach" methods. Many scripts use PathFinding.canReach(), which is horribly inefficient because you can't optimize it. So if you have to use to, try and use it sparingly. Using DPathNavigator is a better approach to making a "can reach" method. With it, you can set a maximum searching distance, so that is will stop performing path finding calculations once it goes too far out. PathFinding.canReach() has no maxmium distance, so it will search the 104x104 map area in an attempt to find a path to the object. The further the path finding algorithm goes, the more costly it becomes, so setting a maximum distance will greatly reduce CPU usage.

 

3. Reducing the Amount/Rate of Objects Created and Disposed

 

Scripts should aim to create a minimal amount of objects which have short lives. The more short-life objects created, the more work the garbage collector must do every cycle, to dispose of those objects.

 

Null Checking as Conditions

 

If you are null checking a returned value of a method as a condition, you should try and call a method which returns an integer, or a boolean, instead of an object to check for the condition, if possible. Example: Using Interfaces.isValidInterface(index) instead of Interfaces.get(index) != null.

 

Caching Returned Values

 

Scripts can cache returned values, instead of calling methods over and over again, which would create tons of new objects.

 

4. Conclusion

 

Less CPU usage equals more clients able to be run which equals happier customers.

Edited by erickho123
Forum Software update made all lines one lined. Attempted to fix it.
1 person likes this

Share this post


Link to post
Share on other sites
		for (RSObject o : doors) {			final RSObjectDefinition def = o.getDefinition();			if (def != null) {				for (final String s : def.getActions()) {					if (s.contains("Open")) {						door = o;						break;					}				}			}		}

Especially when searching for actions such as the String "Open", the string will appear in the beginning.

This is a prime opportunity to use the java method

String startsWith() Method 

I did some testing on the comparison between the "Optimized" method provided, and an altered method utilizing the StartsWith() method.

Using StartsWith() returned 8x faster on average, then the contains method.

 

Warfront1

Edited by warfront1
1 person likes this

Share this post


Link to post
Share on other sites

Shouldn't this be open to the public? I had no idea that waitCondition had only a 5 ms delay, I expected something around 100 ms.

 

 

Path Finding/Can Reach

 

When you need to check alot of objects if they re reachabe, for example:

RSNPC[] monster = NPCs.find(new Filter<RSNPC>() {            @Override            public boolean accept(RSNPC rsnpc) {                return PathFinding.canReach(rsnpc, false);            }        });

Then this will be way more efficient:

    public static boolean[][] getCollisionMap() {        final int[][] flags = PathFinding.getCollisionData();        final RSTile pLoc = Player.getPosition().toLocalTile();        boolean[][] map = new boolean[104][104];        visitTile(map, flags, pLoc.getX(), pLoc.getY());        return map;    }    //recursive tile visitor    private static void visitTile(final boolean[][] map, final int[][] flags, final int x, final int y) {         map[x][y] = true;        if (y > 0 && !map[x][y - 1] // south                && (flags[x][y - 1] & 0x1280102) == 0) {            visitTile(map, flags, x, y - 1);        }        if (x > 0 && !map[x - 1][y] // west                && (flags[x - 1][y] & 0x1280108) == 0) {            visitTile(map, flags, x - 1, y);        }        if (y < 103 && !map[x][y + 1] // north                && (flags[x][y + 1] & 0x1280120) == 0) {            visitTile(map, flags, x, y + 1);        }        if (x < 103 && !map[x + 1][y] // east                && (flags[x + 1][y] & 0x1280180) == 0) {            visitTile(map, flags, x + 1, y);        }    }

Better way:

        final boolean[][] map = MyWalking.getCollisionMap();        RSNPC[] monster = NPCs.find(new Filter<RSNPC>() {            @Override            public boolean accept(RSNPC rsnpc) {                return map[rsnpc.getLocalX()][rsnpc.getLocalY()];            }        });
Edited by AlphaDog

Share this post


Link to post
Share on other sites

Cool tips. I suspected the bit about NPC finding efficacy. I knew that ID would be best, but it was a bit paradoxical because I knew hard coding IDs wasn't ideal. Nice to see that one settled by finding by name (what I do) and then caching the id (I do not currently do).

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.