Quirks & Featuresstable
2 min read time

Documentation

Java interop

Tips and tricks for working around limitations in Java's interop system.

tipsinteropjavaquirkscastingtypesambiguous

What is interop

"interop" is short for "interoperability", which refers to the ability of PixelScript to interact with Java code and libraries. PixelScript is designed to run on the Java Virtual Machine (JVM), which allows it to leverage the vast ecosystem of Java libraries and frameworks.

But, some aspects of JavaScript don't map cleanly to Java, and vice versa. There's a system that will automatically convert between PixelScript and Java types when calling Java methods from PixelScript, and when calling PixelScript functions from Java. This system works well in most cases, but there are some limitations and quirks that developers should be aware of.

Numeric arguments

Java has several different numeric types (int, long, float, double, etc.), while PixelScript has a single Number type that represents all numeric values. When calling Java methods from PixelScript, the interop system will attempt to convert the PixelScript Number to the appropriate Java numeric type based on the method signature.

However, in some cases it's unable to determine the correct type based on context clues. A great example of this is the spawnParticle group of methods in the Bukkit API. There are many overloads of this method that differ only by the types of their numeric arguments (e.g. int vs double). In these cases, the interop system may not be able to determine which overload to call, resulting in an error.

Cases where this happen are rare (and are caused by poor API design in Java), but when they do occur, these casting functions are a lifesaver.

Scripts have a few global functions that can be used to explicitly cast a Number to a specific Java numeric type:

  • asInt(value: Number): Integer
  • asLong(value: Number): Long
  • asFloat(value: Number): Float
  • asDouble(value: Number): Double
  • asShort(value: Number): Short
  • asByte(value: Number): Byte

And with these, you can disambiguate which overload you want to call:

// Without explicit casting, this may result in an error due to ambiguity world.spawnParticle(Particle.FLAME, x, y, z, count, offsetX, offsetY, offsetZ) // With explicit casting, we can specify the exact types we want world.spawnParticle(Particle.FLAME, asDouble(x), asDouble(y), asDouble(z), asInt(count), asDouble(offsetX), asDouble(offsetY), asDouble(offsetZ)) // This is an extreme example, usually only giving 1 type hint is sufficient.
Documentation in early stages. Not meant for public consumption.