[REOPENED] Error executing Module: players.size is not a function
leumasme opened this issue ยท 7 comments
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.
Same thing again, this time all modules are intact @Gloorf
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
Can you please copy/paste the code of betterspawn.js/heal.js/vanish.js so we can try to find the error ?
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 :)