diff --git a/gradle.properties b/gradle.properties
index 6ef13b5..4980d3c 100755
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,5 @@
group=net.gensokyoreimagined.nitori
-version=1.3-SNAPSHOT
+version=1.4-SNAPSHOT
description=Converting patches into mixins, for the Ignite Framework
org.gradle.parallel=true
diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/alloc/MixinIdentifier.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/alloc/MixinIdentifier.java
new file mode 100644
index 0000000..069f6ad
--- /dev/null
+++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/alloc/MixinIdentifier.java
@@ -0,0 +1,34 @@
+package net.gensokyoreimagined.nitori.mixin.alloc;
+
+import net.minecraft.resources.ResourceLocation;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Overwrite;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
+
+@Mixin(ResourceLocation.class)
+public class MixinIdentifier {
+
+ @Shadow @Final
+ private String namespace;
+
+ @Shadow @Final
+ private String path;
+
+ @Unique
+ private String nitori$cachedString = null;
+
+ /**
+ * @author ishland
+ * @reason cache toString
+ */
+ @Overwrite
+ public String toString() {
+ if (this.nitori$cachedString != null) return this.nitori$cachedString;
+ final String s = this.namespace + ":" + this.path;
+ this.nitori$cachedString = s;
+ return s;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/MixinMob.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/micro_opts/MixinMob.java
similarity index 97%
rename from src/main/java/net/gensokyoreimagined/nitori/mixin/MixinMob.java
rename to src/main/java/net/gensokyoreimagined/nitori/mixin/entity/micro_opts/MixinMob.java
index 3a638b9..2862a5e 100644
--- a/src/main/java/net/gensokyoreimagined/nitori/mixin/MixinMob.java
+++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/micro_opts/MixinMob.java
@@ -12,7 +12,7 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-package net.gensokyoreimagined.nitori.mixin;
+package net.gensokyoreimagined.nitori.mixin.entity.micro_opts;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.EntityType;
diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/virtual_thread/DirectVirtualThreadService.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/virtual_thread/DirectVirtualThreadService.java
new file mode 100644
index 0000000..cfd6fdf
--- /dev/null
+++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/virtual_thread/DirectVirtualThreadService.java
@@ -0,0 +1,50 @@
+// Gale - virtual thread support
+
+package net.gensokyoreimagined.nitori.mixin.virtual_thread;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * An implementation of {@link VirtualThreadService} that can create virtual threads directly.
+ *
+ * @author Martijn Muijsers
+ */
+final class DirectVirtualThreadService extends VirtualThreadService {
+
+ private DirectVirtualThreadService() {
+ super();
+ }
+
+ @Override
+ public @NotNull ThreadFactory createFactory() {
+ // Disabled until Minecraft requires servers to have a Java version that can read class files compiled with functionality from Java 19+ on preview / Java 21+ on stable
+ throw new UnsupportedOperationException();
+// return Thread.ofVirtual().factory();
+ }
+
+ @Override
+ public @NotNull Thread start(@NotNull Runnable task) {
+ // Disabled until Minecraft requires servers to have a Java version that can read class files compiled with functionality from Java 19+ on preview / Java 21+ on stable
+ throw new UnsupportedOperationException();
+// Objects.requireNonNull(task, "The task to start a virtual thread cannot be null");
+// return Thread.ofVirtual().start(task);
+ }
+
+ /**
+ * @return A functional {@link DirectVirtualThreadService}.
+ * @throws Throwable If creating virtual threads directly is not supported by the current runtime.
+ * This could be any {@link Throwable}, including an {@link Exception} or an {@link Error}.
+ */
+ static @NotNull DirectVirtualThreadService create() throws Throwable {
+ // Disabled until Minecraft requires servers to have a Java version that can read class files compiled with functionality from Java 19+ on preview / Java 21+ on stable
+ throw new UnsupportedOperationException();
+// var service = new DirectVirtualThreadService();
+// // Run some tests to verify
+// service.runTest();
+// // If we end up here, it works
+// return service;
+ }
+
+}
diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/virtual_thread/ReflectionVirtualThreadService.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/virtual_thread/ReflectionVirtualThreadService.java
new file mode 100644
index 0000000..a5b0cf6
--- /dev/null
+++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/virtual_thread/ReflectionVirtualThreadService.java
@@ -0,0 +1,76 @@
+// Gale - virtual thread support
+
+package net.gensokyoreimagined.nitori.mixin.virtual_thread;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.reflect.Method;
+import java.util.Objects;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * An implementation of {@link VirtualThreadService} that can create virtual threads using Java reflection.
+ *
+ * @author Martijn Muijsers
+ */
+final class ReflectionVirtualThreadService extends VirtualThreadService {
+
+ /**
+ * The {@link Thread}#ofVirtual() method.
+ */
+ private final @NotNull Method Thread_ofVirtual_method;
+
+ /**
+ * The {@link Thread}.Builder#factory() method.
+ */
+ private final @NotNull Method Thread_Builder_factory_method;
+
+ /**
+ * The {@link Thread}.Builder#start(Runnable) method.
+ */
+ private final @NotNull Method Thread_Builder_start_method;
+
+ private ReflectionVirtualThreadService() throws Throwable {
+ this.Thread_ofVirtual_method = Objects.requireNonNull(Thread.class.getMethod("ofVirtual"));
+ // The Thread.Builder class
+ var Thread_Builder_class = Objects.requireNonNull(Class.forName("java.lang.Thread$Builder"));
+ this.Thread_Builder_factory_method = Objects.requireNonNull(Thread_Builder_class.getMethod("factory"));
+ this.Thread_Builder_start_method = Objects.requireNonNull(Thread_Builder_class.getMethod("start", Runnable.class));
+ }
+
+ @Override
+ public @NotNull ThreadFactory createFactory() {
+ try {
+ return (ThreadFactory) this.Thread_Builder_factory_method.invoke(this.Thread_ofVirtual_method.invoke(null));
+ } catch (Exception e) {
+ // This should not be possible because it was tested in create()
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public @NotNull Thread start(@NotNull Runnable task) {
+ Objects.requireNonNull(task, "The task to start a virtual thread cannot be null");
+ try {
+ return (Thread) this.Thread_Builder_start_method.invoke(this.Thread_ofVirtual_method.invoke(null), task);
+ } catch (Exception e) {
+ // This should not be possible because it was tested in create()
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * @return A functional {@link ReflectionVirtualThreadService}.
+ * @throws Throwable If creating virtual threads via reflection is not supported by the current runtime.
+ * This could be any {@link Throwable}, including an {@link Exception} or an {@link Error}.
+ */
+ static @NotNull ReflectionVirtualThreadService create() throws Throwable {
+ // This will already throw something if the reflection fails
+ var service = new ReflectionVirtualThreadService();
+ // Run some tests to verify
+ service.runTest();
+ // If we end up here, it works
+ return service;
+ }
+
+}
diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/virtual_thread/VirtualThreadService.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/virtual_thread/VirtualThreadService.java
new file mode 100644
index 0000000..903f803
--- /dev/null
+++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/virtual_thread/VirtualThreadService.java
@@ -0,0 +1,101 @@
+// Gale - virtual thread support
+
+package net.gensokyoreimagined.nitori.mixin.virtual_thread;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * An abstract service to create virtual threads.
+ *
+ * @author Martijn Muijsers
+ */
+public sealed abstract class VirtualThreadService permits ReflectionVirtualThreadService, DirectVirtualThreadService {
+
+ /**
+ * @return A {@link ThreadFactory} that produces virtual threads.
+ */
+ public abstract @NotNull ThreadFactory createFactory();
+
+ /**
+ * @param task The runnable for the thread to execute.
+ * @return A virtual thread that has been started with the given task.
+ */
+ public abstract @NotNull Thread start(Runnable task);
+
+ /**
+ * Runs a test on the {@link #createFactory} and {@link #start} methods,
+ * which certainly throws some {@link Throwable} if something goes wrong.
+ */
+ protected void runTest() throws Throwable {
+ // This will definitely throw something if it doesn't work
+ try {
+ this.start(() -> {}).join();
+ } catch (InterruptedException ignored) {} // Except InterruptedException, we don't care about that one
+ try {
+ var thread = this.createFactory().newThread(() -> {});
+ thread.start();
+ thread.join();
+ } catch (InterruptedException ignored) {} // Except InterruptedException, we don't care about that one
+ // If we end up here, it works
+ }
+
+ private static boolean initialized = false;
+
+ /**
+ * The {@link VirtualThreadService} for the current runtime,
+ * or null if virtual threads are not supported, or if not {@link #initialized} yet.
+ */
+ private static @Nullable VirtualThreadService implementation;
+
+ /**
+ * @return Whether virtual threads are supported on the current runtime.
+ */
+ public static boolean isSupported() {
+ return get() != null;
+ }
+
+ /**
+ * @return The {@link VirtualThreadService} for the current runtime,
+ * or null if virtual threads are not {@linkplain #isSupported() supported}.
+ *
+ * This method is thread-safe only after the first time it has been fully run.
+ */
+ public static @Nullable VirtualThreadService get() {
+ if (!initialized) {
+ initialized = true;
+ try {
+ implementation = DirectVirtualThreadService.create();
+ } catch (Throwable ignored) {
+ try {
+ implementation = ReflectionVirtualThreadService.create();
+ } catch (Throwable ignored2) {}
+ }
+ }
+ return implementation;
+ }
+
+ /**
+ * The minimum major version of Java that is known to support using virtual threads
+ * (although possibly behind a feature preview flag).
+ */
+ public static final int minimumJavaMajorVersionWithFeaturePreview = 19;
+
+ /**
+ * The minimum major version of Java that is known to support using virtual threads
+ * even without any feature preview flags.
+ */
+ public static final int minimumJavaMajorVersionWithoutFeaturePreview = 21;
+
+ public static int getJavaMajorVersion() {
+ var version = System.getProperty("java.version");
+ if (version.startsWith("1.")) {
+ return version.charAt(2) - '0';
+ }
+ int dotIndex = version.indexOf(".");
+ return Integer.parseInt(dotIndex == -1 ? version : version.substring(0, dotIndex));
+ }
+
+}
diff --git a/src/main/resources/ignite.mod.json b/src/main/resources/ignite.mod.json
index f3c5000..c1b1897 100755
--- a/src/main/resources/ignite.mod.json
+++ b/src/main/resources/ignite.mod.json
@@ -1,6 +1,6 @@
{
"id": "Nitori",
- "version": "1.3-SNAPSHOT",
+ "version": "1.4-SNAPSHOT",
"mixins": [
"mixins.core.json"
]
diff --git a/src/main/resources/mixins.core.json b/src/main/resources/mixins.core.json
index 94f354a..da5967f 100755
--- a/src/main/resources/mixins.core.json
+++ b/src/main/resources/mixins.core.json
@@ -16,7 +16,7 @@
"MixinGameRules",
"MixinIteratorSafeOrderedReferenceSet",
"MixinLevelStorageAccess",
- "MixinMob",
+ "entity.micro_opts.MixinMob",
"MixinNoiseBasedChunkGenerator",
"MixinPlayer",
"MixinPlayerList",
@@ -31,6 +31,7 @@
"alloc.composter.ComposterMixin$ComposterBlockDummyInventoryMixin",
"alloc.composter.ComposterMixin$ComposterBlockFullComposterInventoryMixin",
"alloc.biome_temprature_leak.Biome_threadLocalMixin",
+ "alloc.MixinIdentifier",
"cached_hashcode.BlockNeighborGroupMixin",
"collections.attributes.AttributeContainerMixin",
"collections.block_entity_tickers.WorldChunkMixin",