Search code examples
clojureminecraftbukkitleiningenspigot

Clojure in server plugin dev: calling classes that must be initialized after server startup


I'm new to clojure..

Suppose that a server loads plugins by loading jar file on runtime.

And the server itself has many classes that must be initialized after server startup. (these classes will check if server if running while constructing...)

Can clojure get involved in such a scene?


Here is the detailed

I want to create a Slimefun addon. And I just want to use clojure to setup items

But as we all know, in minecraft dev almost every thing should only be initialized after server start, and the classes will also check if the server is running when constructing (such as ItemStack)

And during clojure compilation, codes seem to be executed. (I don't know if this is right...). That meens if my code involves classes such as ItemStack, then the compilation will fail.

So, if I want to dev spigot plugin involving Clojure, what should I do?


compilation log:

freeze-dolphin@stardust:~/Documents/workspace/idea/Dumortierite/clj-module$ lein jar
Compiling io.sn.dumortierite.clj-module.items
Reflection warning, io/sn/dumortierite/clj_module/items.clj:15:1 - call to io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack ctor can't be resolved.
Syntax error macroexpanding def at (items.clj:15:1).
Execution error (NullPointerException) at org.bukkit.Bukkit/getItemFactory (Bukkit.java:1831).
Cannot invoke "org.bukkit.Server.getItemFactory()" because "org.bukkit.Bukkit.server" is null

Full report at:
/tmp/clojure-2943387361270522517.edn
Compilation failed: Subprocess failed (exit code: 1)

Here is the full report: https://pastebin.com/xzmxHp5q


Also if intrested in how to combine leiningen and gradle you can check out the repo used above: https://github.com/freeze-dolphin/Dumortierite/tree/master/clj-module


Solution

  • Thanks for including the full stacktrace - a lot of newcomers don't, and it's very frustrating because it invariably contains useful information. In this case, it shows that, unsurprisingly, cfrick's guess is right. Specifically, at line 15 of items.clj, you have written

    (def something (whatever (org.bukkit.Bukkit/getItemFactory)))
    

    This means that, during compilation, you are calling getItemFactory. Generally speaking you should depend on very little during compilation. Top level defs should just use function, numbers, strings, sequences, lightweight stuff like that. Anything that you don't need until runtime should be deferred, usually by putting it inside a function defined by defn:

    (defn something [args] (whatever (org.bukkit.Bukkit/getItemFactory)))
    

    which you only call at runtime, once you're ready to run your initialization code.

    If you need something variable-like at the top level despite its expensive dependencies, of course you don't want to initialize it every time you call the function it contains. The easiest solution is to use delay:

    (def something (delay (whatever ...)))
    

    Then you dereference it with @something, which calls whatever only the first time you dereference it.