Skip to content

Element saving techniques

Zezombye edited this page May 6, 2021 · 15 revisions

How elements are calculated

  • One rule takes up one element.
  • One action takes up one element.
  • One rule condition takes up one element.
  • One value takes up one element.
    • The __array__, createWorkshopSetting and evalOnce values take instead 2 elements.
    • For every pair of hero literals in the parameters (nesting included) of an argument of an action or a rule condition, an additional element is taken.
  • One literal takes up one element.
    • Localized strings take instead 2 elements.
  • Each top-level argument of an action or a rule condition takes up one less element than it would otherwise.
  • Disabling rules, actions, or conditions do not change the element count.
  • Custom game settings, comments, rule parameters and variables do not take up elements.

For example, the following code takes up 4 elements:

rule "Rule 1": #1 element
    @Condition isGameInProgress() == true #1 element
    A = 0 #2 elements
  • The rule takes up one element.
  • The condition takes up one element ; both isGameInProgress() and true are functions without arguments, which take one element each, but because they are top-level they take one less element, meaning they effectively have 0 element.
  • The action takes up two elements: A is 0 element because it is top-level, but 0 is 1 element because it is actually Number(0), costing 2-1 = 1 element.

Some constants to keep in mind:

  • A number takes up 2 elements.
  • A global variable access takes up 2 elements.
  • A global variable array index access (such as A[12]) takes up 5 elements.
  • A custom string takes up a minimum of 5 elements, no matter the content.
  • getAllPlayers() takes up 3 elements.
  • A vector takes up 7 elements.

Saving elements

This article will focus on element saving techniques that do not improve the performance (and may actually lower it). For example, replacing 5+3 by 8 saves elements, but it also improves the performance; therefore, it will not be talked in this article.

Numbers

Numbers take up two elements; and that adds up fast. There are ways however to replace some numbers to cost instead 1 element.

Replacing 0 by null

The null value is indistinguishable from 0, except if it is put literally in some functions such as hudText. As such, it is a perfect replacement for 0 and saves one element. Sadly, most input fields will not accept null. OverPy does this automatically when possible.

Replacing 0 by false

In every field where you can input 0, you can also input false, which also saves an element. However, false is not a perfect replacement for 0: it can break some equality checks, breaks when casted to string, and can make some functions bug out, such as random.uniform.

The two known cases where you can neither input false or null without potentially breaking are random.uniform and the start of a range in a for loop. OverPy also does this optimization automatically when possible.

Replacing 0 by a function

For an effective replacement for 0, we must search for functions that would return 0 100% of the time, and that have no argument (else they will not save elements).

The few functions meeting this criteria are:

  • isMatchComplete()
  • getPayloadProgressPercentage() (excluding the Hybrid and Escort gamemodes)
  • getCapturePercentage() (excluding the Assault, Hybrid, and Elimination gamemodes)

You may have wanted to use isInSuddenDeath() as your gamemode is likely not CTF, or even isControlPointLocked() if your gamemode is not Control. However, these functions return "null entity" instead of 0, making random.uniform still bug out, as well as potentially other functions that I haven't tested. Note that the above functions do return 0 instead of false.

As OverPy cannot automatically determine whether it can use these optimizations, they must manually be enabled using the corresponding preprocessing directives.

Replacing 1 by true

There are less available replacements for 1, but one such replacement is true. However, it can only be used in certain cases, and not as a general replacement (such as when assigning to a variable, or in an array).

OverPy will do this optimization on functions that will not be altered by this replacement. You can do this optimization manually on the remaining functions, but be aware of what true does: for example, 2 == true is true.

Replacing 1 by getMatchRound()

Fortunately, there is one function that returns 1 in most gamemodes: getMatchRound(). It can be used as a general replacement if your gamemode meets the criterias (that the round will not change). This optimization can manually be enabled using the corresponding preprocessing directive.

Using match time to store a number

getMatchTime() takes one element and is available everywhere there is a number input, which is perfect for saving elements if you are using the same number multiple times in your code.

Match Time can store any number from 0 to 3599, with decimals. You will have to pauseMatchTime() and setMatchTime(123), making this optimization worth it once your number is repeated more than 3 times (and of course, if you do not care about the actual value of the match time).

getAllPlayers()

getAllPlayers() is used so frequently that OverPy even has a built-in macro for it. Yet it takes 3 elements, as it is All Players(Team(All)).

The only way to save an element is to use a variable that is constantly updated:

globalvar allPlayers

rule "update when someone joins":
    @Event playerJoined
    allPlayers = getAllPlayers()

rule "update when someone leaves":
    @Event playerLeft
    allPlayers = getAllPlayers()

#!define getAllPlayers() allPlayers

The "leaving" rule can be omitted, if and only if you are not doing operations on the array, such as len(), iterations, mappings, etc. This saves an additional 4 elements but is very risky.

This method takes 8 replacements to be effective. It can also be used for getPlayers(Team.1) or getPlayers(Team.2).

For visibility, you can use true as behind the scenes visibility is just player in \<array\>.

Team.1

It is possible, if your gamemode is not playable in Control, to replace Team.1 by getControlScoringTeam(), which will resolve to Team.1 in all gamemodes other than Control. This saves one element; you can apply this replacement using the corresponding preprocessing directive.

Vectors

A vector takes up 7 elements (as it contains 3 numbers), which is pretty high.

We can either:

  • Cache the vector in a variable; the caching takes up 2 elements, which makes this useful as soon as the vector is used twice.
  • Caching in an array can also be done, if the vector is used at least 3 times.
  • Use a built-in constant: null can be used for vect(0,0,0), only in some functions (mostly actions). You can also use Up, Down, etc which are built-in replacements. OverPy does this automatically.

Strings

As with vectors, it is possible to cache a string in a variable, if the same string is used at least twice.

Due to the fact that strings take up the same space no matter if the string literal contains 0 or 128 characters, it can be used for compression of raw data. See Extracting Strings.

Ternary operator

It is possible, in some cases, to replace A if B else C with an and operator. For example:

B if A else null

In this specific case, it can be replaced by:

A and B

If A is falsy, then it will return the value of A (note that A may be [], vect(0,0,0) or false, which is different than null. In visibility fields, though, it does not matter). Else, it will return the value of B.

It would be possible to use [i for i in B if A], which takes the same number of elements. However, it has a worse performance, as it does the calculation for each element of B (if B is an array), and has potential side effects for the value of B.

Sadly, you cannot apply this optimization for visibility fields, as they forbid you from putting an and operator.