ScriptCraft

ScriptCraft

14.6k Downloads

[REOPENED] Error executing Module: players.size is not a function

leumasme opened this issue ยท 7 comments

commented

Had this a few times before, happens when the Server is Starting. If it happens, it can only be fixed by restarting the server, not with js refresh()
Seems to happen randomly without a cause.
image

commented

Note: The given line numbers with the error are wayyy over the actual file size, and players.size is never actually used in the files.

commented

I meant the betterspawn.js module/file, the one which cause erro on refresh() :)

commented

Can you give the code of the bad module ? Would help a lot :)

commented

image
Same thing again, this time all modules are intact @Gloorf

commented

NOTE: this only happens sometimes and is then fixed after restarting the server, after making NO changes to the js files.

betterspawn.js https://paste.ccbluex.net/?view=e6e174aba
heal.js https://paste.ccbluex.net/?view=2eacaa373
vanish.js https://paste.ccbluex.net/?view=ac7c17756
@Gloorf

commented

Can you please copy/paste the code of betterspawn.js/heal.js/vanish.js so we can try to find the error ?

commented

Okay thanks, I think I got it. I'll look into heal.js as it's the shortest file, but it works the same way for other files.

How registering a command works

When you register your command, you do :
command(heal, utils.playerNames())

which register the function 'heal' as a jsp command. The second parameter is actually a list of options for the command, which will be provided to the user using tabCompletion. It can be either an array of string (in which case the options are static and calculated when the command is registered), or a function (in which case the options are dynamic and calculated each time someone /jsp yourcommand + tab).

What does utils.playerNames() truely does

In your case, you give it utils.playerNames() ; digging through utils.js tells us it's actually calling getPlayers, which is calling getPlayersBukkit(). On particular, the line 614 in the file utils.js (in master, in development it's L624, but i'm assuming you are using master) is the following line :

for (var i = 0; i < players.size(); i++){

Now, js Array don't have a size function (you can check by creating an array with var x = []; and trying to call x.size()). If we want to access this information, you use myArray.length in JS. So this is strange it's working at all, right ?

JS regular Array versus Java Collection

But players is not a regular js array ; it's obtained this way :
var players = server.getOnlinePlayers();

and getOnlinePlayers has the following definition (found in spigot doc) :
java.util.Collection<? extends Player> getOnlinePlayers()

So it returns a java Collection, which can be multiple different things (a list, a set, a queue ...). And surprise, a java Collection does have a size function (see Collection javadoc).

So players is actually not a js Array, but a java Collection ! Thanks to the beauty of Nashorn, you can use it like a regular Array, while maintening extra method/parameter from the original Collection. You can check what a JS variable is actually under the hood by using myVar.class : on regular JS var, it will return undefined ; on variable using a java type, it will return the full className.

You can check this on our players variable by typing this in your console :

/js var players = server.getOnlinePlayers(); /js echo(self, players.class);
On my server, it tells me that players.class is "class.java.util.Collections$UnmodifiableRandomAccessList" (but since getOnlinePlayers return a collection, it might be another collection on your computer).

So we know our players is not a regular JS array, and has additional method/parameters, one of which is size ...

What's the bug, then ?

This is the part where i'm struggling because I don't know enough about the internals of spigot/Nashorn/minecraft/Java (don't know where the error truely is). @walterhiggins could you look into this ? I'm actually curious about what is truely happening under the hood !

What we can tell based on the error message :

  • players.size IS defined - when we try to call an undefined function, the error you get is
    Error while trying to evaluate javascript : players.size(), Error: TypeError: Cannot call undefined
  • players.size is NOT a function (hence the error message). And I have bloody no idea what it is :)

The only thing related to this I could find is this commit. If someone wants to dig deeper ... ;)

How do I fix it ?

In ScriptCraft code

I think that the getPlayersBukkit function should use the utils.array that does the conversion from java Array/Collection to JS array without using Collection.size, which would solve the problem.

In your code

Well it's actually pretty easy : as I said in the introduction, you can either provide a function or an array of string as options for tabcompletion. I think the array of string is not what you want in this case : you probably want your command to autocomplete player names based on who is currently connected (i.e in a dynamic way), not based on who is connected when the server is started / the refresh() function is called (i.e in a static way)

You simply need to provide the options as function and not an array of string. Luckily, you already have a function which generate dynamically the list of players, it's utils.getPlayerName ! So simply change your command call to : (notice we use the function name without parenthesis to give the function itself instead of calling the function and giving the return result of the function)

command(heal, utils.playerNames)

Sorry for the long post, but I didn't want to give this solution without explaining how it works, and it became quite lengthy :)