diff --git a/wiki/tutorials/guide/architecture/en.yml b/wiki/tutorials/guide/architecture/en.yml new file mode 100644 index 00000000..2f9b68e3 --- /dev/null +++ b/wiki/tutorials/guide/architecture/en.yml @@ -0,0 +1,2 @@ +title: "KubeJS Architecture" +description: "How KubeJS works: layers from the OS to the JS runtime" diff --git a/wiki/tutorials/guide/architecture/meta.yml b/wiki/tutorials/guide/architecture/meta.yml new file mode 100644 index 00000000..7eee91f9 --- /dev/null +++ b/wiki/tutorials/guide/architecture/meta.yml @@ -0,0 +1,2 @@ +# Part 1 of Pie's guide; see also the guide index +see-also: "/tutorials/guide" diff --git a/wiki/tutorials/guide/architecture/page.kubedoc b/wiki/tutorials/guide/architecture/page.kubedoc new file mode 100644 index 00000000..a66f271f --- /dev/null +++ b/wiki/tutorials/guide/architecture/page.kubedoc @@ -0,0 +1,293 @@ +# Programmers Guide to KubeJS's Architecture + +This guide assumes you have some base level information about general programming paradigms like Object Oriented Programming (OOP) and basic level JavaScript syntax. + +## Layers + +It's important to understand that KubeJS is not the same as NodeJS or browser JavaScript. +To understand why let's break down the layers that make KubeJS possible. + +### Layer 0: Computer OS + +Your computer OS doesn't play much of a difference, with the exception of file paths. +If you aren't doing file operations then skip ahead to the next layer. + +When writing scripts that read and write data, it is important to understand that Windows file system and Linux file system have different path structures. +All paths should be written in a relative file syntax. +Example: `"world/data/example.nbt"` +Avoid using absolute paths like: `"/world/data/example.nbt"` (Linux) or `"C://Users/Example/Curse/…"` (Windows) +Note the difference between a "relative" path and an "absolute" path is based on two things: + +Prefix: +- The Prefix `C://` is the drive letter prefix on a windows machine. Not everyone installs things to the default C letter drive. And the full path is often dependent on the Username set when windows is installed. +- The Prefix `/` denotes an absolute path on Unix file systems. Minecraft modpacks get installed to their own folder. The user has full control over this folder, so naturally what yours is set to will not apply to every user. +- The Prefix `./` is typically used to denote a relative path as well. Relative to where is the tricky part. It ultimately depends on context, but the simplest way is to simply drop this part of the path altogether. +Dropping the `./` in the path can help with edge cases where somebody takes one path and concatenates it with the provided path, resulting in a broken path altogether. + +Slash Direction: +- Try to avoid putting back slack `\` in your paths whenever possible, in favor of forward slash `/`. When copying paths directly from windows, you may find that it uses a back slash instead of a forward slash. +There are some exceptions, when you need to "escape" a special character, but that is a technical edge case that won't pop-up often enough to worry about. + +### Layer 1: Java Virtual Machine (JVM) + +Minecraft *Java* edition runs on top of the *Java* Virtual Machine. +Java as a whole is both a programming language and a machine code language. +The programming language (files that end in `.java`) are compiled by the java compiler into machine readable code (files that end in `.class`). +This does not happen at runtime (while the game is launched), this instead happens when a project compiles their source code into byte code to form a single archive file (`.jar`). +These files are in a byte code format that specific Java versions can load into the JVM. +Java compresses and archives groups of these `.class` files (as well as other files added by each app) into a single `.jar` file. You can open these files in tools like 7zip or WinRAR as if they were like any other zip file. + +When you launch Minecraft, in the background, the Java executable `javaw.exe` is being used on a mixed combination of archive files (`.jar`) and class files (`.class`). (Different OSes may use different files). +The version of the game, mod loader library, and installation method all effect what exactly is ran. + +### Layer 2: Class Loader + +When the JVM is created, it loads all the class files into memory using something called a ClassLoader. This class loader manages what classes exist in the JVM runtime, and where they can be access from. +They also help manage accessing resources. So if extra files were compiled into a jar file, the class loader can be used to access them. Examples such as mod provided resource packs, textures, data-packs, and other assets. + +Different mod loaders such as Fabric or NeoForge have the means of controlling these class loaders and implementing their own. + +They do this for a few reasons. The first reason is so they can modify vanilla class data to make it easier work with for mod authors. +This process of injecting instructions into classes before they are loaded is called runtime byte code modification. +There is a library called ASM that makes modifying byte code quite a bit easier. And another library named Mixins is built on top of ASM to make it even easier for Mods to inject their own instructions, interfaces, and fields into existing classes they wouldn't normally have access to. + +In a similar vein there are things called Access Wideners, which change the "access modifiers" on particular fields and methods so that they can be accessed by a mod. +It's a tool to break Java's encapsulated design because some code was not designed with external modification in mind. + +Typically when mod loaders start up, they scan the mods folder to find any and all mods that contain their own mixin instructions and access widener instructions. + +After collecting these mixins, they then attempt to apply and load the mod's class data in a particular order. +The only way they know what classes *are* mixins is because of a config file included in the jar that points the modloader to the mixin classes. +(Location and definition varies by modloader). + +The purpose of Mixins is to make accessible, what was not originally accessible. +If the code was not originally designed in a way that let you modify it, you are often times forced to use mixins to control how the underlying logic behaves. + +### Layer 3: Mod Layer + +Each Minecraft mod is made up of a single compiled archive file (`.jar`) that is tailored to the mod loader it was compiled for. (That is why you typically see Fabric, Forge, and NeoForge as separate downloads). + +Each mod loader defines a different standard for how the mod loader expects a mod to be initialized and hooked into the Minecraft runtime. +Each mod loader also provides a different set of tools for interacting with the based minecraft code. +Typically what is common for each mod loader is that they define a set of events that can be hooked into and listened to. +Allowing for mods to apply changes, register new content, and cancel events from occurring altogether. +Not every possible scenario will have an event defined by the mod loader. For example, when a block is placed during terrain generation. +This is done on purpose to optimize performance and is often delegated to individual mods to handle the more specific situations. +Every instruction adds up over time, and the longer it takes to execute, the easier it is for a player to notice performance issues. + +There are libraries to help make developing for multiple modloaders easier such as Architectury. +Where they define a "common" layer that is compatible with multiple mod loaders, and then compiled differently for each mod loader. +It does this by defining multiple sub-projects within one gradle project. +With a single common project that is used as a dependency for each version specific project. +You won't need to learn or understand gradle to learn KubeJS. + +KubeJS used Architectury library up until 1.21 when it dropped support for Fabric. +This means most source code for previous versions will exist in the "common" folder on GitHub, with modloader specific code existing in the fabric and forge folders respectively. + +Also, prior to 1.18.2, all Mojang provided Minecraft code was obfuscated and so all methods, fields, and class names for Minecraft data had to be wrapped in an object like `EntityJS`, `ItemStackJS`, `BlockContainerJS`, etc… +Without these, you'd have field names like: `f_32270_` which is supposed to be `Creeper.swell`. + +Starting in 1.18.2, Lat added Rhino Remapping which made it so that official mapping files would be downloaded, and used to map fields, classes, and methods to their human readable versions. +And the `EntityJS` and `ItemStackJS` classes would eventually become Mixins to add additional functionality to the Minecraft classes they originally wrapped. + +Starting in 1.21, This remapping became unnecessary. + +And in Minecraft 26.1, Mojang stopped obfuscating their source code, which made this process unnecessary, and improved the modding experience as a whole. + +### Layer 4: JS Interpreter Layer + +Rhino is a library ***originally*** maintained by Mozilla. This library has an entire JavaScript interpreter that runs on top of the Java runtime. +Lat made a copy of this library (forked the code) several years back and made several modifications to it in order to make it fit the Minecraft ecosystem more easily. + +Because of this, the version of Rhino referred to here, is the one Lat modified and maintains. **Mozilla is not affiliated with the Minecraft mod Rhino in any way.** +This library is LARGE and hard to maintain, which is why Lat made an effort to rewrite it in the form of Ichor. But this library is likely going to stay a prototype for a while and not directly integrated with KubeJS for the foreseeable future. + +Rhino uses Java's Reflection library to call access data, execute functions, and create proxy classes during runtime. +What this means is that Rhino can take an object of an unknown type, determine what methods it can call, what fields it has access to, and manipulate them as needed. +This ability makes it possible for Rhino translate text in the style of JavaScript, and translate that to instructions within the JVM. +This is why you may have seen some people say that KubeJS is not the exact same as JavaScript. +This is because Rhino does not implement every standard JavaScript feature and does not have a "JS runtime" like most JS applications do. + +The five most important things to understand about KubeJS are: + +- Script Types +- Bindings + Events +- Type Wrappers +- Beans +- Functional Interface Proxies + +### Script Type + +Script Types are fairly straight forward. +They are used to separate logic by mod life cycle. +For example, startup scripts are used when the client or server is first starting up. +You can think of this as a mod initialization phase. +There is quite a lot of Minecraft logic that both the client and server need to agree on to be compatible. +For example, the items, blocks, and entities that exist in the game. +If the client is missing an item that the server registered at startup, then the client will not be compatible with the server. + +On the opposite side, there are also some things that should only occur on the client side or server side, but never both. + +For example, a server is not concerned with the rendering of the game. +It does not need to care about visuals, as long as it knows the physical attributes of everything such as hit box size, position, rotation, etc… + +That is why Server Scripts and Client Scripts exist. +They allow for code to be run specifically for clients and specifically for servers. + +If you play Single Player, you are technically running both Client and Server Scripts at the same time. +This is because Single Player uses an "Internal Server". +It's the same reason why you can open up your single player world to LAN. +All it is doing is exposing the port the internal server was running on. + +You'll sometimes see oddities in startup code where an event fires twice, once for client and once for server. + +### Bindings + +Bindings are a way for KubeJS and addons to add script-type based global constants. +It is the fundamental reason why KubeJS can connect Minecraft to JS code. +By adding a binding like: "ItemEvents" to a script type, it enables the JS script to reference Java code from the JS file. +Rhino knows that anytime you type `ItemEvents` in JS, you want to do something with that specific java object. + +Bindings are what links JavaScript to Java. + +Bindings are also responsible for providing your script with the ability to listen to Minecraft events. + +From 1.16.5 to 1.18.2 there was a single binding that let you listen to events called `onEvent(string, handler)` where the string was an arbitrarily defined id which was really hard to document automatically. This was because the strings did not need to be registered and listening to these events made it impossible to detect typos and mistakes. + +Fast forward to 1.19+ and now we have Event Groups like `BlockEvents` and `ClientEvents` which have script type based event handlers which are easier to generate documentation for. +For example KubeJS Offline Documentation can scan the registered Bindings for these event handlers. + +[KubeJS Offline](https://hunter19823.github.io/kubejsoffline/1.20.1/forge/#?focus=global-bindings-header&global-bindings-expanded=true) + +### Type Wrappers + +JavaScript is a loosely typed language. +What this means is that you do not need to explicitly declare what type a variable or value is in order to store and use it. +Functions, fields, methods may not care what type of value you are passing to them. + +Java is a strictly typed language. +Which means that you always (with very few exceptions) need to declare the type of variable you are storing, accepting, or returning. +If you have a method that expects a specific class, you must *always* provide an object that inherits from that class. + +If you are already familiar with these two concepts, you may already see the issue with going between Java and JavaScript. +Which is why Rhino has the super neat and handy feature called "TypeWrappers". +TypeWrappers are a way for Java mods to convert JS values and objects to a specific Java type. +For example, if a Java function requires an ItemStack, KubeJS provides a Type Wrapper that will convert text like "minecraft:stick" into an ItemStack of size 1. If you pass in an Item, it will make an ItemStack out of it. +TypeWrappers are everywhere and make working with Java objects from JS much much easier. + +Rhino will automatically convert Strings to Enumerate values, if the string exactly matches the ENUM's name. + +In 1.19.2+ Rhino will automatically map JSON objects to Records. + +There is one exception which is Ambiguous arguments. + +Ambiguous argument exceptions occur when you have overloaded java methods, where both methods support type wrapping in their arguments. +The edge case stems from this question: +You have one method accepts a string. +You have another method, with the same name, but that accepts a ResourceLocation. +How do you know which method to use, if both methods support type wrapping? + +Anything you pass from the JS context is loosely typed. Which means you can't make assumptions about what type is being intentionally passed to Java from JS. + +Rhino will throw an exception in these cases and the solution to them typically involves using the fully qualified method name on the object. + +See the example below: + +```java +public interface Example { + public String getNameOfItem(ResourceLocation itemId); + + public String getNameOfItem(ItemStack itemStackInstance); +} +``` + +And if you were to try and use this from the JS side: + +```js +example.getNameOfItem("minecraft:stick"); +example.getNameOfItem(Item.of("minecraft:stick")); +``` + +In the example above, you might think that this would be a valid way of distinguishing the two methods. +The problem, is the translation layer that Rhino implements. + +Rhino could convert the string, "minecraft:stick", into a ResourceLocation because there is a type wrapper registered for doing exactly that. +But Rhino could just as easily convert that same string into an ItemStack, because there is also a type wrapper registered for converting strings into ItemStacks as well. + +The fact that both support converting a string into an ItemStack isn't the issue here. The issue is that we have two identically named methods, with different parameters that have different type wrappers registered. +Rhino does not know how to prioritize one over the other, so it chooses to fail in such cases. + +### Beans + +In short Beans are just syntactical sugar that make it easier to write setter and getter methods. + +Anytime a Java object has .setX and .getX, on the JS side you can reference X as if it were a field and mutate it like a field. + +There is a wiki entry regarding beans: [[/tips/beans]]. + +### Functional Interface Proxies + +This one is a bit tricky to understand, but put simply: + +There is a global type wrapper that converts JS functions into Java Functional Interfaces automatically. + +A functional interface in Java, is an Interface with a single, non-default, non static, method. + +Here is an example: + +```java +package com.example; + +public static class ExampleClass { + // Example Class fields. + private int a; + private int b; + + // Constructor that takes two numbers; + public ExampleClass(int a, int b) { + this.a = a; + this.b = b; + } + // Definition of the Functional Interface + // The @FunctionalInterface is not required in java, just know that if you see this annotation it is safe to assume it is a functional interface. + @FunctionalInterface + public interface MagicHandler { + // The method that one would need to provide from JS. + public int applyMagic(int a, int b); + } + // Static method that accepts the Functional Interface + public static int performMagic(MagicHandler magicHandler) { + return magicHandler.applyMagic(9, 10); + } + // Non Static Method that applies magic to the MagicHandler's fields a and b. + public int performMagicOnThisObject(MagicHandler magicHandler) { + return magicHandler.applyMagic(this.a, this.b); + } +} +``` + +You can call this method in any of the following ways: + +```js +const $EXAMPLE = Java.loadClass('com.example.ExampleClass'); +// "performMagic" is a Static method on Example Class inside the com.example package. +// Which means that I can load the class using the Java binding and call it's static method via Rhino's Reflection. + +// Here is an example using JS Lambda functions +$EXAMPLE.performMagic((a, b) => return a * b); +// Here is an example using JS functions +$EXAMPLE.performMagic(function(a, b) { + return a + b; +}); +// Here is an example of storing this function and then using it. +function magicDivide(a, b) { + return a / b; +} +$EXAMPLE.performMagic(magicDivide); +// Let's now make an instance of the ExampleClass and call the non-static performMagicTwice method +const EXAMPLE_INSTANCE = new $EXAMPLE(1, 2); +// Call the non-static method on the newly made ExampleClass object. +EXAMPLE_INSTANCE.performMagicOnThisObject(magicDivide); +``` + +Functional Interfaces are the reason you can listen to events in KubeJS at all. All event handlers are just FunctionalInterfaces. diff --git a/wiki/tutorials/guide/documentation/en.yml b/wiki/tutorials/guide/documentation/en.yml new file mode 100644 index 00000000..955cda43 --- /dev/null +++ b/wiki/tutorials/guide/documentation/en.yml @@ -0,0 +1,2 @@ +title: "KubeJS Documentation" +description: "Where is the KubeJS documentation? Why is it not in the KubeJS Wiki?" diff --git a/wiki/tutorials/guide/documentation/meta.yml b/wiki/tutorials/guide/documentation/meta.yml new file mode 100644 index 00000000..7eee91f9 --- /dev/null +++ b/wiki/tutorials/guide/documentation/meta.yml @@ -0,0 +1,2 @@ +# Part 1 of Pie's guide; see also the guide index +see-also: "/tutorials/guide" diff --git a/wiki/tutorials/guide/documentation/page.kubedoc b/wiki/tutorials/guide/documentation/page.kubedoc new file mode 100644 index 00000000..54dd2a67 --- /dev/null +++ b/wiki/tutorials/guide/documentation/page.kubedoc @@ -0,0 +1,168 @@ +# Where is the documentation? + +## Pretext + +A common critique of KubeJS is that there isn't much documentation. If you are used to mods that have their own custom language, it can really feel like there isn't any documentation. +The tricky part is that KubeJS is not a single language. Nor is it a new language. +In fact, it is a hybrid between Java and JavaScript. Which means the syntax, grammar, and style are almost entirely JavaScript. To the point where programming tools like VSCode and IntelliJ will treat it as JavaScript. +But the actual semantics and the logic behind how everything works are based in Java. +Which ultimately means it has the versatility of Java modding with the simplicity of JavaScript programming. + +>>> info +Fun fact, this is actually why AI has such a hard time understanding KubeJS. +AI training data is primarily based on available source code. +Which means that AI will always lean towards traditional javascript code examples and patterns, rather than KubeJS specific ones. +This is also why it is easier to spot AI generated code, as it will produce code that has no basis in reality, making it very easy to spot. +This is why the community has been vocally against AI generated code. +<<< + +But therein lies the crux of the problem. Because it has the versatility of Java modding, it is capable of doing more than can be practically documented. +So a decision needed to be made. + +## History + +Back in 2021, Max, one of KubeJS's maintainers, made a great post discussing the possible ways to document KubeJS and the challenges each one presents. +That post is now gone from GitHub, but it was captured on the Wayback Machine in 2024. If you are interested in reading through it, you can find it [here](https://web.archive.org/web/20240629153432/https://github.com/KubeJS-Mods/KubeJS/discussions/113). +But to summarize that post, there were three primary camps of thought for how to handle documentation. +And two secondary camps that have become popular in recent years. + +1. Wiki-based Documentation (KubeJS Wiki) +2. Generated Documentation (Aurora / Borealis / KubeJS Offline Documentation) +3. IDE Support (VSCode / IntelliJ + ProbeJS) +4. Leveraging Existing Sources (GitHub Source Code, Modding Documentation, Minecraft Wiki, etc...) +5. In-Code Documentation (JavaDocs / Code Comments) + +>>> info +And as it turns out, the best solution is not a single one, but a mix of all of them! +<<< + +### 1. Wiki-based Documentation + +As you already guessed, the wiki you are reading right now was one of the proposed solutions. +And with the help of the community, the wiki has improved significantly over the years. +Almost all of the information in the wiki is maintained by the community, who are not paid to maintain it. + +Which brings me to the reason for having the wiki in the first place. +Sometimes you need to explain things in layman's terms. +Sometimes you need to provide a simple code snippet for people to copy and paste to get started. +The wiki is a great way to do this. +But because it needs to be updated by hand, it has some massive drawbacks. +It takes time to update the wiki, time that could be spent on improving the API itself. +With each Minecraft release, the internals change quite a bit. Which can necessitate changes to the API itself. +The more content that is added to the wiki, the more work it becomes to maintain it and keep it up to date. +For most of KubeJS's life, it was a multi-mod loader mod. Which meant that Fabric and Forge were both supported. +And that meant each mod loader behaved differently and offered different capabilities. +It is simply impossible to cover every single capability of KubeJS in a wiki format, and keep it up to date. +And for those reasons, the wiki itself cannot be the primary source of documentation for KubeJS. +But it can be a great starting point for learning the basics of KubeJS. +Every major modpack that uses KubeJS does not rely solely on the wiki for documentation. +Later in this guide, I will explain how to use the other solutions to supplement the wiki and do more than simply copy/paste code snippets. + +### 2. Generated Documentation + +Back in Minecraft 1.12.2, Lat created a tool called Aurora. +This tool would create a simple webserver that would generate a javadoc-like webpage at runtime. +Back in the day, before any automatic remapping, this was essential for knowing what methods, fields, and classes were available to you. +At this point wikis were basically non-existent, and this was one of the few ways to get documentation for KubeJS. + +After Minecraft 1.16, I ported Aurora in the form of Borealis. +I added a few features to it, improved the information displayed, and added some functionality to allow for custom webserver features. +But in the end, having to expose an additional port to get documentation was not a viable solution. +There were security concerns with exposing an additional port. +And you had to have the game running to get documentation. +I found myself trying to help people on Discord from my phone, and didn't want to have to fire up the game just to help someone. + +>>> info +Fun Fact: There are studies that show teaching others something is the best way to retain the information yourself. +I wouldn't have known as much about KubeJS as I do now if I hadn't spent several years helping others learn how to use it. +<<< + +After Minecraft 1.18.2, I created KubeJS Offline Documentation. This tool was the successor to Borealis. +Rather than running a webserver, I instead scanned the Java runtime for all the available classes, methods, and fields. +I compressed the information into a single JSON file and then used it to generate a static html page. +The idea was that it was completely self-contained and, once generated, could be used completely offline. +In this format, I was also able to add searching functionality and relational analysis between classes, so you can quickly dig through thousands of classes for what you need. +I will explain some of the advanced ways to use this tool later in this guide. +The way that I can support every single javadoc in a single html file is by clearing and creating the UI elements on demand. +This way, if you click on a link, nothing needs to be reloaded, and the page will update in real time. + +Over time, I found myself including more and more information in that JSON file, and constantly fighting with the size of the file. +Saving the entire Java runtime into a single JSON file is not exactly simple or space efficient. +Most javadoc generators generate a single html file for each class. +I had to find unique ways to compress the information, deduplicate data, and optimize decompression. +If I unpack too much data, the browser will run out of memory and crash. +If I try to unpack too little data, the searching and relational analysis will not work as expected. +So the current solution I came up with was to decompress chunks of data, index, and store them in the browser's local storage database. +Once that process is complete, the relational analysis can be quickly loaded from the database, which minimizes the time it takes to load the page. + +I will admit that KubeJS Offline Documentation is still not a perfect solution. +It won't suggest code completions, it doesn't provide working code snippets, and I am not a UI designer. +It is not a replacement for the wiki, and it is not a replacement for IDE support. +But it was designed to solve a specific problem, and if used correctly, can save you a lot of time debugging. + +### 3. IDE Support + +As I mentioned earlier, KubeJS is a hybrid between Java and JavaScript. +Which means that the syntax, grammar, and style are almost entirely JavaScript. +But the type system is majority Java. +And if you know anything about the JavaScript type system, you know that it is very loose and flexible. +Which means that for an IDE to provide meaningful suggestions, it would need to have a lot of context about the code. +And that is where TypeScript comes in. +TypeScript is a superset of JavaScript that adds static typing to the language. +>>> info +A superset language is a language that is based on another language, but adds additional features to it. +For example, TypeScript is a superset of JavaScript. +It is a language that is compiled to JavaScript, and is therefore compatible with all JavaScript environments. +<<< + +Most modern IDEs support TypeScript as their primary means of understanding JavaScript code. +Which meant that the next logical step was to generate TypeScript definitions for KubeJS. +And that is where ProbeJS comes in. +ProbeJS is a tool created by Prunoideae, a Bioinformatics researcher and KubeJS contributor. +Prunoideae created ProbeJS to generate TypeScript definitions for KubeJS, and helped improve how documentation was generated for KubeJS. +He helped push for annotation driven documentation, which is a way to pass information about KubeJS source code to the Java runtime. +This allowed for TypeScript definitions to be generated with real documentation, and not just type information. +Additionally, it helped those reading the source code to have a better understanding of the code, and how it works. + +Over time, ProbeJS has been updated, improved, and now supports providing much more than simple type information. +It can suggest item ids, block ids, and even display icons for items and blocks. + +The downside of this kind of documentation, however, is not the fault of ProbeJS, but the fault of the TypeScript servers themselves. +TypeScript servers are not designed to handle the entire Java runtime's worth of type definitions. +They are also not designed to handle TypeWrappers, which are a Rhino-specific feature. +This means that ProbeJS has to limit the amount of information it generates and 'guess' what types you are likely to be using. +This is often why you may be missing certain suggestions, or why your IDE is hogging your memory. + +### 4. Leveraging Existing Sources + +One of the reasons KubeJS is so popular is because it is a very versatile mod. +It can interface with other mods, allowing for a lot of flexibility and customization. +This can be used to your advantage when you are trying to find documentation on how to do something with a specific mod. +For example, finding documentation on JEI plugins or Jade plugins for KubeJS may be somewhat difficult. +But JEI and Jade both have their own documentation for making a plugin for their respective mods. +And by utilizing their documentation, you only really need to understand where the connection points are between KubeJS and the other mod. + +### 5. In-Code Documentation + +Java has a feature called documentation comments, which are a way to document Java code. +There are tools shipped with Java installations to generate JavaDocs from documentation comments, and this is often the way large software projects document their code. +The only downside is that this information does not stick around in the compiled byte code, and is not able to be used to generate documentation at runtime. +Java also has a feature called annotations, which isn't just for documentation, but can be used to define additional information about the code that is available at runtime. +KubeJS leverages this feature so that tools like ProbeJS and KubeJS Offline Documentation can enrich their own documentation at runtime. + +When you become comfortable reading source code, it unlocks a whole new world of possibilities. +You can start to understand how the code works, and how it is designed. +You can also start to diagnose issues with your code more easily, and solve problems more independently. +The more veteran you become, the more you will find yourself reading the source code of other mods to understand how they work. + +>>> info +Fun Fact: The most common reason for people to start reading source code is to understand how to fix a bug in their own scripts. +This is because when you are stuck on a problem, and you are not able to find a solution, reading the source code of the mod or library you are using can often provide the answer you are looking for. +<<< + + +## Conclusion + +As you can see, there are many different ways to document KubeJS. +And no single solution is a one-size-fits-all solution for all use cases. +The best way to understand KubeJS is to use a combination of all of these solutions together. \ No newline at end of file diff --git a/wiki/tutorials/guide/en.yml b/wiki/tutorials/guide/en.yml new file mode 100644 index 00000000..399b6ce9 --- /dev/null +++ b/wiki/tutorials/guide/en.yml @@ -0,0 +1,2 @@ +title: "General KubeJS Guides" +description: "KubeJS Tutorial Guides made by members of the KubeJS community" \ No newline at end of file diff --git a/wiki/tutorials/guide/pie/en.yml b/wiki/tutorials/guide/pie/en.yml new file mode 100644 index 00000000..5cbb6f48 --- /dev/null +++ b/wiki/tutorials/guide/pie/en.yml @@ -0,0 +1,2 @@ +title: "Pie's KubeJS Guide" +description: "A guide to KubeJS made by ILIKEPIEFOO2 / @PieTheNiceGuy on Discord" \ No newline at end of file diff --git a/wiki/tutorials/guide/pie/page.kubedoc b/wiki/tutorials/guide/pie/page.kubedoc new file mode 100644 index 00000000..1134a814 --- /dev/null +++ b/wiki/tutorials/guide/pie/page.kubedoc @@ -0,0 +1,9 @@ +# Pie's KubeJS Guide + +Part 1 of this community guide explains **how KubeJS works** from the ground up: the layers from your OS and the JVM through the mod loader and Rhino to the JavaScript you write. It is aimed at programmers and assumes basic OOP and JavaScript. + +**[[/tutorials/guide/architecture|Part 1: KubeJS Architecture]]** — Layers (OS, JVM, Class Loader, Mod Implementation, JS/Rhino), script types, bindings and events, type wrappers, beans, and functional interface proxies. + +**[[/tutorials/guide/documentation|Part 2: Where is the documentation?]]** — Wiki, generated docs, IDE support, existing sources, and in-code documentation. + +**[[/tutorials/guide/reading-kubejs|Part 3: Reading KubeJS]]** — Script structure, server/client/startup scripts, and tracing from script to Java source. \ No newline at end of file diff --git a/wiki/tutorials/guide/reading-kubejs/en.yml b/wiki/tutorials/guide/reading-kubejs/en.yml new file mode 100644 index 00000000..f65da274 --- /dev/null +++ b/wiki/tutorials/guide/reading-kubejs/en.yml @@ -0,0 +1,2 @@ +title: "Reading KubeJS" +description: "How to read KubeJS scripts and write KubeJS on your own." diff --git a/wiki/tutorials/guide/reading-kubejs/meta.yml b/wiki/tutorials/guide/reading-kubejs/meta.yml new file mode 100644 index 00000000..7eee91f9 --- /dev/null +++ b/wiki/tutorials/guide/reading-kubejs/meta.yml @@ -0,0 +1,2 @@ +# Part 1 of Pie's guide; see also the guide index +see-also: "/tutorials/guide" diff --git a/wiki/tutorials/guide/reading-kubejs/page.kubedoc b/wiki/tutorials/guide/reading-kubejs/page.kubedoc new file mode 100644 index 00000000..5ac1d8fd --- /dev/null +++ b/wiki/tutorials/guide/reading-kubejs/page.kubedoc @@ -0,0 +1,175 @@ +# Reading KubeJS + +An important part of becoming a proficient KubeJS developer is being able to read and understand KubeJS scripts. +This guide will cover the basics of reading KubeJS scripts and how to debug them on your own. + +## Prerequisites + +- Knowing how to access your modpack's "instance" folder. (The folder that contains your modpack's data.) +- Understanding how to save a javascript `.js` file to your computer. +- todo: add prerequisites + +## Understanding the KubeJS Script Structure + +KubeJS scripts are stored in the `instance` folder of your modpack in the `kubejs` folder. +Inside the `kubejs` folder, you will find a folder for each script type. +Where a script is located is important because it determines what events and bindings are available to the script. +A common source of confusion is when a script is located in the wrong folder, or outside of any of the correct folders. +This can not only cause the script to not run, but can also cause the script to run twice, not at all, or crash the game entirely. + +>>> info +If you are ever asked in the KubeJS Discord server to provide your script, make sure to include the folder it is located in. +This can help greatly in diagnosing the problem. +<<< + +Each script type has a specific purpose and is designed to be used in a specific way. +Knowing it's use can help you better debug your scripts to make sure they are in the right place. + +### Server Scripts + +Server scripts are some of the most common scripts you will find in a KubeJS modpack. +They are scripts that are run whenever you start a single player world, or run a dedicated minecraft server. +Server scripts are stored in the `server_scripts` folder. +With some additional configuration, you can also use server scripts when a client does not have kubejs installed. +Server scripts are used to control the server-side logic of your modpack. + +Including, but not limited to: + - Interaction related events + - Block placed + - Block broken + - Item used + - Food eaten + - Player specific events + - Player joins the server + - Player leaves the server + - Player interacts with a block + - Player interacts with an item + - Player interacts with a entity + - Player performing some action every game tick. + - Entity specific events + - Entity attempts to spawn + - Entity spawns + - Entity dies + - Entity performing some action every game tick. + - World ("Level") specific events + - World generating + - World loaded + - World saving + - World unloading + - Dimension loading + - Dimension loading + - Dimension unloading + - Chunk or Section related events + - Entities going in and out of a chunk or section + - Server specific events + - Server starting + - Server stopping + - Server performing some action every game tick. + - Data related events + - Recipe related events + - Loot table related events + - Advancement related events + - Tag related events + +### Client Scripts + +Client scripts are scripts that are run whenever you are in a single player world, or on a client connected to a server. +Client scripts are stored in the `client_scripts` folder. +Client scripts can run even when the server you are connected to doesn't have kubejs installed. +This is ussually the least common script type, and is only used in very specific cases. +This script type will do nothing when placed on a dedicated server. + +The goal of client scripts is to control the client-side logic of your game. +Including, but not limited to: + - Render specific events + - A server doesn't need to render a block to know it exists, but a client does. + - Localization related information + - Changing the language of the game, updating names of items, blocks, etc... + - Translating text into the language of the player. + - Client-side specific events + - Receiving a chat message from the server + - Hiding JEI items. + - Registering custom JEI categories. + + +### Startup Scripts + +Startup scripts are scripts that are run whenever the game is started. +Startup scripts are stored in the `startup_scripts` folder. +Startup scripts are used to control the startup logic of your modpack. +They run on both the client and server, and often types support bindings from other script types. +This script type is known to produce some unexpected behavior in single-player worlds, such as events firing twice. +This is because the client and server both trigger the event, and the event is fired twice in total. + +Anything related to registering new content should be placed in a startup script. +The reason for this is that the game starts up in a very specific order, and registering new content is part of the startup process. +If you register new content on a server, and try to join the server from a client that does not share the same content, the client will be rejected. +This is because Minecraft forces the client and server to agree on what content is registered before joining a world. + +This script type includes, but is not limited to: + - Registering items, blocks, entities, etc... + - Registering listeners to "Native" events + - Client-server synchronized events. + - Such as canceling the placement of a block. + + +## Deep diving into a simple example + +Let's take a look at a simple example of a KubeJS script. + +The following script is located in the `startup_scripts` folder, and is named `example.js`. + +```js +PlayerEvents.chat(event => { + // Check if message equals creeper, ignoring case + if (event.message.trim().toLowerCase() == 'creeper') { + // Schedule task in 1 tick, because if you reply immediately, it will print before player's message + event.server.scheduleInTicks(1, event.server, ctx => { + // Tell everyone "Aw man", colored green. Callback data is the server + ctx.data.tell(Text.green('Aw man')) + }) + } +}) +``` + +Let's break down what this script does and identify where it is connected to the java code. + +### Event Group + +Starting with the first line, we see `[js]PlayerEvents.chat(event => {`. +The `PlayerEvents` object is a server-side event group, provided to the script as a binding. +How do I know this? +KubeJS uses a plugin system to register the bindings and events available to the script. +And in each version it ships a "BuiltinKubeJSPlugin" class that registers the common bindings and events. +In 1.19.2, the binding for `PlayerEvents` is defined in this Plugin class: [BuiltinKubeJSPlugin](https://github.com/KubeJS-Mods/KubeJS/blob/1902/common/src/main/java/dev/latvian/mods/kubejs/plugin/builtin/BuiltinKubeJSPlugin.java#L250). +In 1.20.1, it was also defined in the same Plugin class: [BuiltinKubeJSPlugin](https://github.com/KubeJS-Mods/KubeJS/blob/2001/common/src/main/java/dev/latvian/mods/kubejs/plugin/builtinBuiltinKubeJSPlugin.java#L261). +In 1.21.1, the location where it was defined changed, and the way you register Event groups changed, but it is more or less the same still: [BuiltinKubeJSPlugin](https://github.com/KubeJS-Mods/KubeJS/blob/99655ced4fbb3627ed5d2a50745cc525a4161c7c/src/main/java/dev/latvian/mods/kubejs/plugin/builtin/BuiltinKubeJSPlugin.java#L402). + +If you were using KubeJS Offline Documentation, you would find this information on the homepage. + - [1.19.2 Forge](https://hunter19823.github.io/kubejsoffline/1.19.2/forge/#?focus=global-bindings-PlayerEvents&dev.latvian.mods.kubejs.event.EventJS-expanded=false&global-bindings-expanded=true) + - [1.19.2 Fabric](https://hunter19823.github.io/kubejsoffline/1.19.2/fabric/#?focus=global-bindings-PlayerEvents&dev.latvian.mods.kubejs.event.EventJS-expanded=false&global-bindings-expanded=true) + - [1.20.1 Forge](https://hunter19823.github.io/kubejsoffline/1.20.1/forge/#?focus=global-bindings-PlayerEvents&dev.latvian.mods.kubejs.event.EventJS-expanded=false&global-bindings-expanded=true) + - [1.20.1 Fabric](https://hunter19823.github.io/kubejsoffline/1.20.1/fabric/#?focus=global-bindings-PlayerEvents&dev.latvian.mods.kubejs.event.EventJS-expanded=false&global-bindings-expanded=true) + - TODO: 1.21 and 1.21.1 links here. + +Now that we know where PlayerEvents comes from, what exactly is it? What can I do with it? +In order to answer that question, we need to find the source code for the `PlayerEvents` class itself. + +In 1.19.2, the source code for the `PlayerEvents` class is located in this file: [PlayerEvents](https://github.com/KubeJS-Mods/KubeJS/blob/1902/common/src/main/java/dev/latvian/mods/kubejs/bindings/event/PlayerEvents.java#L38). +In 1.20.1, the source code for the `PlayerEvents` class was also located in this file: [PlayerEvents](https://github.com/KubeJS-Mods/KubeJS/blob/2001/common/src/main/java/dev/latvian/mods/kubejs/bindings/event/PlayerEvents.java#L38). +In 1.21.1, the source code for the `PlayerEvents` class was moved to this file: [PlayerEvents](https://github.com/KubeJS-Mods/KubeJS/blob/2101/src/main/java/dev/latvian/mods/kubejs/plugin/builtin/event/PlayerEvents.java#L25). + +>>> info +How did I find the source code for the `PlayerEvents` class in github? +It is almost universal that every java class imports the classes it uses. +Which means that you can find the "package" name of the class near the top of the plugin file. +Example: [import dev.latvian.mods.kubejs.plugin.builtin.event.PlayerEvents;](https://github.com/KubeJS-Mods/KubeJS/blob/99655ced4fbb3627ed5d2a50745cc525a4161c7c/src/main/java/dev/latvian/mods/kubejs/plugin/builtin/BuiltinKubeJSPlugin.java#L80) +This means that, starting from the `src` folder, you can navigate to the `dev/latvian/mods/kubejs/plugin/builtin/event` folder, and find the `PlayerEvents.java` file inside it. +<<< + +### Event Handler + +Now that we know where the `PlayerEvents` class is located, we can take a look at the `chat` method. +The `chat` method is special kind of method that is provided to the script like it was always a property of the `PlayerEvents` class. +It takes a single argument, a function that is called when a player sends a chat message. +That function is called an "event handler". \ No newline at end of file