aboutsummaryrefslogtreecommitdiff
path: root/spark-fabric/src
diff options
context:
space:
mode:
Diffstat (limited to 'spark-fabric/src')
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java161
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java75
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/FabricServerConfigProvider.java57
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/FabricWorldInfoProvider.java178
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ClientEntityManagerAccessor.java40
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ClientWorldAccessor.java36
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/MinecraftClientAccessor.java34
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ServerEntityManagerAccessor.java40
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ServerWorldAccessor.java36
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java183
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricClientSparkPlugin.java29
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java52
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkMixinPlugin.java71
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java24
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/smap/MixinUtils.java52
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceDebugCache.java87
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMap.java133
-rw-r--r--spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMapProvider.java53
-rw-r--r--spark-fabric/src/main/resources/fabric.mod.json3
-rw-r--r--spark-fabric/src/main/resources/spark.mixins.json15
20 files changed, 1188 insertions, 171 deletions
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java
index 7030680..51834fc 100644
--- a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java
@@ -22,18 +22,35 @@ package me.lucko.spark.fabric;
import com.google.common.collect.ImmutableMap;
-import me.lucko.spark.common.util.ClassSourceLookup;
+import me.lucko.spark.common.sampler.source.ClassSourceLookup;
+import me.lucko.spark.common.util.ClassFinder;
+import me.lucko.spark.fabric.smap.MixinUtils;
+import me.lucko.spark.fabric.smap.SourceMap;
+import me.lucko.spark.fabric.smap.SourceMapProvider;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.objectweb.asm.Type;
+import org.spongepowered.asm.mixin.FabricUtil;
+import org.spongepowered.asm.mixin.extensibility.IMixinConfig;
+import org.spongepowered.asm.mixin.transformer.Config;
+import org.spongepowered.asm.mixin.transformer.meta.MixinMerged;
+
+import java.lang.reflect.Method;
+import java.net.URI;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Map;
public class FabricClassSourceLookup extends ClassSourceLookup.ByCodeSource {
+
+ private final ClassFinder classFinder = new ClassFinder();
+ private final SourceMapProvider smapProvider = new SourceMapProvider();
+
private final Path modsDirectory;
- private final Map<Path, String> pathToModMap;
+ private final Map<String, String> pathToModMap;
public FabricClassSourceLookup() {
FabricLoader loader = FabricLoader.getInstance();
@@ -43,7 +60,7 @@ public class FabricClassSourceLookup extends ClassSourceLookup.ByCodeSource {
@Override
public String identifyFile(Path path) {
- String id = this.pathToModMap.get(path);
+ String id = this.pathToModMap.get(path.toAbsolutePath().normalize().toString());
if (id != null) {
return id;
}
@@ -55,11 +72,141 @@ public class FabricClassSourceLookup extends ClassSourceLookup.ByCodeSource {
return super.identifyFileName(this.modsDirectory.relativize(path).toString());
}
- private static Map<Path, String> constructPathToModIdMap(Collection<ModContainer> mods) {
- ImmutableMap.Builder<Path, String> builder = ImmutableMap.builder();
+ @Override
+ public @Nullable String identify(MethodCall methodCall) throws Exception {
+ String className = methodCall.getClassName();
+ String methodName = methodCall.getMethodName();
+ String methodDesc = methodCall.getMethodDescriptor();
+
+ if (className.equals("native") || methodName.equals("<init>") || methodName.equals("<clinit>")) {
+ return null;
+ }
+
+ Class<?> clazz = this.classFinder.findClass(className);
+ if (clazz == null) {
+ return null;
+ }
+
+ Class<?>[] params = getParameterTypesForMethodDesc(methodDesc);
+ Method reflectMethod = clazz.getDeclaredMethod(methodName, params);
+
+ MixinMerged mixinMarker = reflectMethod.getDeclaredAnnotation(MixinMerged.class);
+ if (mixinMarker == null) {
+ return null;
+ }
+
+ return modIdFromMixinClass(mixinMarker.mixin());
+ }
+
+ @Override
+ public @Nullable String identify(MethodCallByLine methodCall) throws Exception {
+ String className = methodCall.getClassName();
+ String methodName = methodCall.getMethodName();
+ int lineNumber = methodCall.getLineNumber();
+
+ if (className.equals("native") || methodName.equals("<init>") || methodName.equals("<clinit>")) {
+ return null;
+ }
+
+ SourceMap smap = this.smapProvider.getSourceMap(className);
+ if (smap == null) {
+ return null;
+ }
+
+ int[] inputLineInfo = smap.getReverseLineMapping().get(lineNumber);
+ if (inputLineInfo == null || inputLineInfo.length == 0) {
+ return null;
+ }
+
+ for (int fileInfoIds : inputLineInfo) {
+ SourceMap.FileInfo inputFileInfo = smap.getFileInfo().get(fileInfoIds);
+ if (inputFileInfo == null) {
+ continue;
+ }
+
+ String path = inputFileInfo.path();
+ if (path.endsWith(".java")) {
+ path = path.substring(0, path.length() - 5);
+ }
+
+ String possibleMixinClassName = path.replace('/', '.');
+ if (possibleMixinClassName.equals(className)) {
+ continue;
+ }
+
+ return modIdFromMixinClass(possibleMixinClassName);
+ }
+
+ return null;
+ }
+
+ private static String modIdFromMixinClass(String mixinClassName) {
+ for (Config config : MixinUtils.getMixinConfigs().values()) {
+ IMixinConfig mixinConfig = config.getConfig();
+ if (mixinClassName.startsWith(mixinConfig.getMixinPackage())) {
+ return mixinConfig.getDecoration(FabricUtil.KEY_MOD_ID);
+ }
+ }
+ return null;
+ }
+
+ private Class<?>[] getParameterTypesForMethodDesc(String methodDesc) {
+ Type methodType = Type.getMethodType(methodDesc);
+ Class<?>[] params = new Class[methodType.getArgumentTypes().length];
+ Type[] argumentTypes = methodType.getArgumentTypes();
+
+ for (int i = 0, argumentTypesLength = argumentTypes.length; i < argumentTypesLength; i++) {
+ Type argumentType = argumentTypes[i];
+ params[i] = getClassFromType(argumentType);
+ }
+
+ return params;
+ }
+
+ private Class<?> getClassFromType(Type type) {
+ return switch (type.getSort()) {
+ case Type.VOID -> void.class;
+ case Type.BOOLEAN -> boolean.class;
+ case Type.CHAR -> char.class;
+ case Type.BYTE -> byte.class;
+ case Type.SHORT -> short.class;
+ case Type.INT -> int.class;
+ case Type.FLOAT -> float.class;
+ case Type.LONG -> long.class;
+ case Type.DOUBLE -> double.class;
+ case Type.ARRAY -> {
+ final Class<?> classFromType = getClassFromType(type.getElementType());
+ Class<?> result = classFromType;
+ if (classFromType != null) {
+ for (int i = 0; i < type.getDimensions(); i++) {
+ result = result.arrayType();
+ }
+ }
+ yield result;
+ }
+ case Type.OBJECT -> this.classFinder.findClass(type.getClassName());
+ default -> null;
+ };
+ }
+
+ private static Map<String, String> constructPathToModIdMap(Collection<ModContainer> mods) {
+ ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
for (ModContainer mod : mods) {
- Path path = mod.getRootPath().toAbsolutePath().normalize();
- builder.put(path, mod.getMetadata().getId());
+ String modId = mod.getMetadata().getId();
+ if (modId.equals("java")) {
+ continue;
+ }
+
+ for (Path path : mod.getRootPaths()) {
+ URI uri = path.toUri();
+ if (uri.getScheme().equals("jar") && path.toString().equals("/")) { // ZipFileSystem
+ String zipFilePath = path.getFileSystem().toString();
+ builder.put(zipFilePath, modId);
+ } else {
+ builder.put(path.toAbsolutePath().normalize().toString(), modId);
+ }
+
+ }
}
return builder.build();
}
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java
new file mode 100644
index 0000000..22794c2
--- /dev/null
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java
@@ -0,0 +1,75 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package me.lucko.spark.fabric;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+import me.lucko.spark.common.platform.MetadataProvider;
+
+import net.minecraft.resource.ResourcePackManager;
+import net.minecraft.resource.ResourcePackProfile;
+import net.minecraft.resource.ResourcePackSource;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class FabricExtraMetadataProvider implements MetadataProvider {
+
+ private final ResourcePackManager resourcePackManager;
+
+ public FabricExtraMetadataProvider(ResourcePackManager resourcePackManager) {
+ this.resourcePackManager = resourcePackManager;
+ }
+
+ @Override
+ public Map<String, JsonElement> get() {
+ Map<String, JsonElement> metadata = new LinkedHashMap<>();
+ metadata.put("datapacks", datapackMetadata());
+ return metadata;
+ }
+
+ private JsonElement datapackMetadata() {
+ JsonObject datapacks = new JsonObject();
+ for (ResourcePackProfile profile : this.resourcePackManager.getEnabledProfiles()) {
+ JsonObject obj = new JsonObject();
+ obj.addProperty("name", profile.getDisplayName().getString());
+ obj.addProperty("description", profile.getDescription().getString());
+ obj.addProperty("source", resourcePackSource(profile.getSource()));
+ datapacks.add(profile.getName(), obj);
+ }
+ return datapacks;
+ }
+
+ private static String resourcePackSource(ResourcePackSource source) {
+ if (source == ResourcePackSource.NONE) {
+ return "none";
+ } else if (source == ResourcePackSource.BUILTIN) {
+ return "builtin";
+ } else if (source == ResourcePackSource.WORLD) {
+ return "world";
+ } else if (source == ResourcePackSource.SERVER) {
+ return "server";
+ } else {
+ return "unknown";
+ }
+ }
+}
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricServerConfigProvider.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricServerConfigProvider.java
new file mode 100644
index 0000000..325a324
--- /dev/null
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricServerConfigProvider.java
@@ -0,0 +1,57 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package me.lucko.spark.fabric;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import me.lucko.spark.common.platform.serverconfig.ConfigParser;
+import me.lucko.spark.common.platform.serverconfig.PropertiesConfigParser;
+import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider;
+
+import java.util.Collection;
+import java.util.Map;
+
+public class FabricServerConfigProvider extends ServerConfigProvider {
+
+ /** A map of provided files and their type */
+ private static final Map<String, ConfigParser> FILES;
+ /** A collection of paths to be excluded from the files */
+ private static final Collection<String> HIDDEN_PATHS;
+
+ public FabricServerConfigProvider() {
+ super(FILES, HIDDEN_PATHS);
+ }
+
+ static {
+ ImmutableSet.Builder<String> hiddenPaths = ImmutableSet.<String>builder()
+ .add("server-ip")
+ .add("motd")
+ .add("resource-pack")
+ .add("rcon<dot>password")
+ .add("level-seed")
+ .addAll(getSystemPropertyList("spark.serverconfigs.hiddenpaths"));
+
+ FILES = ImmutableMap.of("server.properties", PropertiesConfigParser.INSTANCE);
+ HIDDEN_PATHS = hiddenPaths.build();
+ }
+
+}
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricWorldInfoProvider.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricWorldInfoProvider.java
new file mode 100644
index 0000000..156db89
--- /dev/null
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricWorldInfoProvider.java
@@ -0,0 +1,178 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package me.lucko.spark.fabric;
+
+import it.unimi.dsi.fastutil.longs.LongIterator;
+import it.unimi.dsi.fastutil.longs.LongSet;
+
+import me.lucko.spark.common.platform.world.AbstractChunkInfo;
+import me.lucko.spark.common.platform.world.CountMap;
+import me.lucko.spark.common.platform.world.WorldInfoProvider;
+import me.lucko.spark.fabric.mixin.ClientEntityManagerAccessor;
+import me.lucko.spark.fabric.mixin.ClientWorldAccessor;
+import me.lucko.spark.fabric.mixin.ServerEntityManagerAccessor;
+import me.lucko.spark.fabric.mixin.ServerWorldAccessor;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.world.ClientEntityManager;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.EntityType;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.world.ServerEntityManager;
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.util.math.ChunkPos;
+import net.minecraft.world.entity.EntityIndex;
+import net.minecraft.world.entity.EntityTrackingSection;
+import net.minecraft.world.entity.SectionedEntityCache;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.stream.Stream;
+
+public abstract class FabricWorldInfoProvider implements WorldInfoProvider {
+
+ protected List<FabricChunkInfo> getChunksFromCache(SectionedEntityCache<Entity> cache) {
+ LongSet loadedChunks = cache.getChunkPositions();
+ List<FabricChunkInfo> list = new ArrayList<>(loadedChunks.size());
+
+ for (LongIterator iterator = loadedChunks.iterator(); iterator.hasNext(); ) {
+ long chunkPos = iterator.nextLong();
+ Stream<EntityTrackingSection<Entity>> sections = cache.getTrackingSections(chunkPos);
+
+ list.add(new FabricChunkInfo(chunkPos, sections));
+ }
+
+ return list;
+ }
+
+ public static final class Server extends FabricWorldInfoProvider {
+ private final MinecraftServer server;
+
+ public Server(MinecraftServer server) {
+ this.server = server;
+ }
+
+ @Override
+ public CountsResult pollCounts() {
+ int players = this.server.getCurrentPlayerCount();
+ int entities = 0;
+ int chunks = 0;
+
+ for (ServerWorld world : this.server.getWorlds()) {
+ ServerEntityManager<Entity> entityManager = ((ServerWorldAccessor) world).getEntityManager();
+ EntityIndex<?> entityIndex = ((ServerEntityManagerAccessor) entityManager).getIndex();
+
+ entities += entityIndex.size();
+ chunks += world.getChunkManager().getLoadedChunkCount();
+ }
+
+ return new CountsResult(players, entities, -1, chunks);
+ }
+
+ @Override
+ public ChunksResult<FabricChunkInfo> pollChunks() {
+ ChunksResult<FabricChunkInfo> data = new ChunksResult<>();
+
+ for (ServerWorld world : this.server.getWorlds()) {
+ ServerEntityManager<Entity> entityManager = ((ServerWorldAccessor) world).getEntityManager();
+ SectionedEntityCache<Entity> cache = ((ServerEntityManagerAccessor) entityManager).getCache();
+
+ List<FabricChunkInfo> list = getChunksFromCache(cache);
+ data.put(world.getRegistryKey().getValue().getPath(), list);
+ }
+
+ return data;
+ }
+ }
+
+ public static final class Client extends FabricWorldInfoProvider {
+ private final MinecraftClient client;
+
+ public Client(MinecraftClient client) {
+ this.client = client;
+ }
+
+ @Override
+ public CountsResult pollCounts() {
+ ClientWorld world = this.client.world;
+ if (world == null) {
+ return null;
+ }
+
+ ClientEntityManager<Entity> entityManager = ((ClientWorldAccessor) world).getEntityManager();
+ EntityIndex<?> entityIndex = ((ClientEntityManagerAccessor) entityManager).getIndex();
+
+ int entities = entityIndex.size();
+ int chunks = world.getChunkManager().getLoadedChunkCount();
+
+ return new CountsResult(-1, entities, -1, chunks);
+ }
+
+ @Override
+ public ChunksResult<FabricChunkInfo> pollChunks() {
+ ChunksResult<FabricChunkInfo> data = new ChunksResult<>();
+
+ ClientWorld world = this.client.world;
+ if (world == null) {
+ return null;
+ }
+
+ ClientEntityManager<Entity> entityManager = ((ClientWorldAccessor) world).getEntityManager();
+ SectionedEntityCache<Entity> cache = ((ClientEntityManagerAccessor) entityManager).getCache();
+
+ List<FabricChunkInfo> list = getChunksFromCache(cache);
+ data.put(world.getRegistryKey().getValue().getPath(), list);
+
+ return data;
+ }
+ }
+
+ static final class FabricChunkInfo extends AbstractChunkInfo<EntityType<?>> {
+ private final CountMap<EntityType<?>> entityCounts;
+
+ FabricChunkInfo(long chunkPos, Stream<EntityTrackingSection<Entity>> entities) {
+ super(ChunkPos.getPackedX(chunkPos), ChunkPos.getPackedZ(chunkPos));
+
+ this.entityCounts = new CountMap.Simple<>(new HashMap<>());
+ entities.forEach(section -> {
+ if (section.getStatus().shouldTrack()) {
+ section.stream().forEach(entity ->
+ this.entityCounts.increment(entity.getType())
+ );
+ }
+ });
+ }
+
+ @Override
+ public CountMap<EntityType<?>> getEntityCounts() {
+ return this.entityCounts;
+ }
+
+ @Override
+ public String entityTypeName(EntityType<?> type) {
+ return EntityType.getId(type).toString();
+ }
+ }
+
+}
+
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ClientEntityManagerAccessor.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ClientEntityManagerAccessor.java
new file mode 100644
index 0000000..994c9a3
--- /dev/null
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ClientEntityManagerAccessor.java
@@ -0,0 +1,40 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package me.lucko.spark.fabric.mixin;
+
+import net.minecraft.client.world.ClientEntityManager;
+import net.minecraft.entity.Entity;
+import net.minecraft.world.entity.EntityIndex;
+import net.minecraft.world.entity.SectionedEntityCache;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(ClientEntityManager.class)
+public interface ClientEntityManagerAccessor {
+
+ @Accessor
+ SectionedEntityCache<Entity> getCache();
+
+ @Accessor
+ EntityIndex<?> getIndex();
+
+}
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ClientWorldAccessor.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ClientWorldAccessor.java
new file mode 100644
index 0000000..01562ef
--- /dev/null
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ClientWorldAccessor.java
@@ -0,0 +1,36 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package me.lucko.spark.fabric.mixin;
+
+import net.minecraft.client.world.ClientEntityManager;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.entity.Entity;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(ClientWorld.class)
+public interface ClientWorldAccessor {
+
+ @Accessor
+ ClientEntityManager<Entity> getEntityManager();
+
+}
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/MinecraftClientAccessor.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/MinecraftClientAccessor.java
new file mode 100644
index 0000000..7a4fb78
--- /dev/null
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/MinecraftClientAccessor.java
@@ -0,0 +1,34 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package me.lucko.spark.fabric.mixin;
+
+import net.minecraft.client.MinecraftClient;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(MinecraftClient.class)
+public interface MinecraftClientAccessor {
+
+ @Accessor
+ Thread getThread();
+
+}
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ServerEntityManagerAccessor.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ServerEntityManagerAccessor.java
new file mode 100644
index 0000000..2c67502
--- /dev/null
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ServerEntityManagerAccessor.java
@@ -0,0 +1,40 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package me.lucko.spark.fabric.mixin;
+
+import net.minecraft.entity.Entity;
+import net.minecraft.server.world.ServerEntityManager;
+import net.minecraft.world.entity.EntityIndex;
+import net.minecraft.world.entity.SectionedEntityCache;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(ServerEntityManager.class)
+public interface ServerEntityManagerAccessor {
+
+ @Accessor
+ SectionedEntityCache<Entity> getCache();
+
+ @Accessor
+ EntityIndex<?> getIndex();
+
+}
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ServerWorldAccessor.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ServerWorldAccessor.java
new file mode 100644
index 0000000..cf2e7e8
--- /dev/null
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ServerWorldAccessor.java
@@ -0,0 +1,36 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package me.lucko.spark.fabric.mixin;
+
+import net.minecraft.entity.Entity;
+import net.minecraft.server.world.ServerEntityManager;
+import net.minecraft.server.world.ServerWorld;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(ServerWorld.class)
+public interface ServerWorldAccessor {
+
+ @Accessor
+ ServerEntityManager<Entity> getEntityManager();
+
+}
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java
index dc2e7d9..69303e3 100644
--- a/spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/placeholder/SparkFabricPlaceholderApi.java
@@ -20,169 +20,48 @@
package me.lucko.spark.fabric.placeholder;
-import eu.pb4.placeholders.PlaceholderAPI;
-import eu.pb4.placeholders.PlaceholderResult;
+import eu.pb4.placeholders.api.PlaceholderContext;
+import eu.pb4.placeholders.api.PlaceholderHandler;
+import eu.pb4.placeholders.api.PlaceholderResult;
+import eu.pb4.placeholders.api.Placeholders;
import me.lucko.spark.common.SparkPlatform;
-import me.lucko.spark.common.monitor.cpu.CpuMonitor;
-import me.lucko.spark.common.monitor.tick.TickStatistics;
-import me.lucko.spark.common.util.RollingAverage;
-import me.lucko.spark.common.util.StatisticFormatter;
+import me.lucko.spark.common.util.SparkPlaceholder;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
-public class SparkFabricPlaceholderApi {
- private final SparkPlatform platform;
+import org.jetbrains.annotations.Nullable;
- public SparkFabricPlaceholderApi(SparkPlatform platform) {
- this.platform = platform;
+public enum SparkFabricPlaceholderApi {
+ ;
- PlaceholderAPI.register(
- new Identifier("spark", "tps"),
- context -> {
- TickStatistics tickStatistics = platform.getTickStatistics();
- if (tickStatistics == null) {
- return PlaceholderResult.invalid();
- }
-
- if (context.hasArgument()) {
- Double tps = switch (context.getArgument()) {
- case "5s":
- yield tickStatistics.tps5Sec();
- case "10s":
- yield tickStatistics.tps10Sec();
- case "1m":
- yield tickStatistics.tps1Min();
- case "5m":
- yield tickStatistics.tps5Min();
- case "15m":
- yield tickStatistics.tps15Min();
- default:
- yield null;
- };
-
- if (tps == null) {
- return PlaceholderResult.invalid("Invalid argument");
- } else {
- return PlaceholderResult.value(toText(StatisticFormatter.formatTps(tps)));
- }
- } else {
- return PlaceholderResult.value(toText(
- Component.text()
- .append(StatisticFormatter.formatTps(tickStatistics.tps5Sec())).append(Component.text(", "))
- .append(StatisticFormatter.formatTps(tickStatistics.tps10Sec())).append(Component.text(", "))
- .append(StatisticFormatter.formatTps(tickStatistics.tps1Min())).append(Component.text(", "))
- .append(StatisticFormatter.formatTps(tickStatistics.tps5Min())).append(Component.text(", "))
- .append(StatisticFormatter.formatTps(tickStatistics.tps15Min()))
- .build()
- ));
- }
- }
- );
-
- PlaceholderAPI.register(
- new Identifier("spark", "tickduration"),
- context -> {
- TickStatistics tickStatistics = platform.getTickStatistics();
- if (tickStatistics == null || !tickStatistics.isDurationSupported()) {
- return PlaceholderResult.invalid();
- }
-
- if (context.hasArgument()) {
- RollingAverage duration = switch (context.getArgument()) {
- case "10s":
- yield tickStatistics.duration10Sec();
- case "1m":
- yield tickStatistics.duration1Min();
- default:
- yield null;
- };
-
- if (duration == null) {
- return PlaceholderResult.invalid("Invalid argument");
- } else {
- return PlaceholderResult.value(toText(StatisticFormatter.formatTickDurations(duration)));
- }
- } else {
- return PlaceholderResult.value(toText(
- Component.text()
- .append(StatisticFormatter.formatTickDurations(tickStatistics.duration10Sec())).append(Component.text("; "))
- .append(StatisticFormatter.formatTickDurations(tickStatistics.duration1Min()))
- .build()
- ));
- }
- }
- );
-
- PlaceholderAPI.register(
- new Identifier("spark", "cpu_system"),
- context -> {
- if (context.hasArgument()) {
- Double usage = switch (context.getArgument()) {
- case "10s":
- yield CpuMonitor.systemLoad10SecAvg();
- case "1m":
- yield CpuMonitor.systemLoad1MinAvg();
- case "15m":
- yield CpuMonitor.systemLoad15MinAvg();
- default:
- yield null;
- };
-
- if (usage == null) {
- return PlaceholderResult.invalid("Invalid argument");
- } else {
- return PlaceholderResult.value(toText(StatisticFormatter.formatCpuUsage(usage)));
- }
- } else {
- return PlaceholderResult.value(toText(
- Component.text()
- .append(StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad10SecAvg())).append(Component.text(", "))
- .append(StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad1MinAvg())).append(Component.text(", "))
- .append(StatisticFormatter.formatCpuUsage(CpuMonitor.systemLoad15MinAvg()))
- .build()
- ));
- }
- }
- );
-
- PlaceholderAPI.register(
- new Identifier("spark", "cpu_process"),
- context -> {
- if (context.hasArgument()) {
- Double usage = switch (context.getArgument()) {
- case "10s":
- yield CpuMonitor.processLoad10SecAvg();
- case "1m":
- yield CpuMonitor.processLoad1MinAvg();
- case "15m":
- yield CpuMonitor.processLoad15MinAvg();
- default:
- yield null;
- };
-
- if (usage == null) {
- return PlaceholderResult.invalid("Invalid argument");
- } else {
- return PlaceholderResult.value(toText(StatisticFormatter.formatCpuUsage(usage)));
- }
- } else {
- return PlaceholderResult.value(toText(
- Component.text()
- .append(StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad10SecAvg())).append(Component.text(", "))
- .append(StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad1MinAvg())).append(Component.text(", "))
- .append(StatisticFormatter.formatCpuUsage(CpuMonitor.processLoad15MinAvg()))
- .build()
- ));
- }
- }
- );
+ public static void register(SparkPlatform platform) {
+ for (SparkPlaceholder placeholder : SparkPlaceholder.values()) {
+ Placeholders.register(
+ new Identifier("spark", placeholder.getName()),
+ new Handler(platform, placeholder)
+ );
+ }
}
- private Text toText(Component component) {
- return Text.Serializer.fromJson(GsonComponentSerializer.gson().serialize(component));
+ private record Handler(SparkPlatform platform, SparkPlaceholder placeholder) implements PlaceholderHandler {
+ @Override
+ public PlaceholderResult onPlaceholderRequest(PlaceholderContext context, @Nullable String argument) {
+ return toResult(this.placeholder.resolve(this.platform, argument));
+ }
+
+ private static PlaceholderResult toResult(Component component) {
+ return component == null
+ ? PlaceholderResult.invalid()
+ : PlaceholderResult.value(toText(component));
+ }
+
+ private static Text toText(Component component) {
+ return Text.Serializer.fromJson(GsonComponentSerializer.gson().serialize(component));
+ }
}
+
}
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricClientSparkPlugin.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricClientSparkPlugin.java
index e94d697..faf4eef 100644
--- a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricClientSparkPlugin.java
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricClientSparkPlugin.java
@@ -28,14 +28,20 @@ import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
+import me.lucko.spark.common.platform.MetadataProvider;
import me.lucko.spark.common.platform.PlatformInfo;
+import me.lucko.spark.common.platform.world.WorldInfoProvider;
+import me.lucko.spark.common.sampler.ThreadDumper;
import me.lucko.spark.common.tick.TickHook;
import me.lucko.spark.common.tick.TickReporter;
import me.lucko.spark.fabric.FabricCommandSender;
+import me.lucko.spark.fabric.FabricExtraMetadataProvider;
import me.lucko.spark.fabric.FabricPlatformInfo;
import me.lucko.spark.fabric.FabricSparkMod;
import me.lucko.spark.fabric.FabricTickHook;
import me.lucko.spark.fabric.FabricTickReporter;
+import me.lucko.spark.fabric.FabricWorldInfoProvider;
+import me.lucko.spark.fabric.mixin.MinecraftClientAccessor;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
@@ -55,10 +61,12 @@ public class FabricClientSparkPlugin extends FabricSparkPlugin implements Comman
}
private final MinecraftClient minecraft;
+ private final ThreadDumper.GameThread gameThreadDumper;
public FabricClientSparkPlugin(FabricSparkMod mod, MinecraftClient minecraft) {
super(mod);
this.minecraft = minecraft;
+ this.gameThreadDumper = new ThreadDumper.GameThread(() -> ((MinecraftClientAccessor) minecraft).getThread());
}
@Override
@@ -87,7 +95,6 @@ public class FabricClientSparkPlugin extends FabricSparkPlugin implements Comman
return 0;
}
- this.threadDumper.ensureSetup();
this.platform.executeCommand(new FabricCommandSender(context.getSource().getEntity(), this), args);
return Command.SINGLE_SUCCESS;
}
@@ -113,6 +120,16 @@ public class FabricClientSparkPlugin extends FabricSparkPlugin implements Comman
}
@Override
+ public void executeSync(Runnable task) {
+ this.minecraft.executeSync(task);
+ }
+
+ @Override
+ public ThreadDumper getDefaultThreadDumper() {
+ return this.gameThreadDumper.get();
+ }
+
+ @Override
public TickHook createTickHook() {
return new FabricTickHook.Client();
}
@@ -123,6 +140,16 @@ public class FabricClientSparkPlugin extends FabricSparkPlugin implements Comman
}
@Override
+ public MetadataProvider createExtraMetadataProvider() {
+ return new FabricExtraMetadataProvider(this.minecraft.getResourcePackManager());
+ }
+
+ @Override
+ public WorldInfoProvider createWorldInfoProvider() {
+ return new FabricWorldInfoProvider.Client(this.minecraft);
+ }
+
+ @Override
public PlatformInfo getPlatformInfo() {
return new FabricPlatformInfo(PlatformInfo.Type.CLIENT);
}
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java
index 428ac4c..1606d57 100644
--- a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java
@@ -30,15 +30,22 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import me.lucko.fabric.api.permissions.v0.Permissions;
import me.lucko.spark.common.monitor.ping.PlayerPingProvider;
+import me.lucko.spark.common.platform.MetadataProvider;
import me.lucko.spark.common.platform.PlatformInfo;
+import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider;
+import me.lucko.spark.common.platform.world.WorldInfoProvider;
+import me.lucko.spark.common.sampler.ThreadDumper;
import me.lucko.spark.common.tick.TickHook;
import me.lucko.spark.common.tick.TickReporter;
import me.lucko.spark.fabric.FabricCommandSender;
+import me.lucko.spark.fabric.FabricExtraMetadataProvider;
import me.lucko.spark.fabric.FabricPlatformInfo;
import me.lucko.spark.fabric.FabricPlayerPingProvider;
+import me.lucko.spark.fabric.FabricServerConfigProvider;
import me.lucko.spark.fabric.FabricSparkMod;
import me.lucko.spark.fabric.FabricTickHook;
import me.lucko.spark.fabric.FabricTickReporter;
+import me.lucko.spark.fabric.FabricWorldInfoProvider;
import me.lucko.spark.fabric.placeholder.SparkFabricPlaceholderApi;
import net.fabricmc.loader.api.FabricLoader;
@@ -59,10 +66,12 @@ public class FabricServerSparkPlugin extends FabricSparkPlugin implements Comman
}
private final MinecraftServer server;
+ private final ThreadDumper gameThreadDumper;
public FabricServerSparkPlugin(FabricSparkMod mod, MinecraftServer server) {
super(mod);
this.server = server;
+ this.gameThreadDumper = new ThreadDumper.Specific(server.getThread());
}
@Override
@@ -74,7 +83,11 @@ public class FabricServerSparkPlugin extends FabricSparkPlugin implements Comman
// placeholders
if (FabricLoader.getInstance().isModLoaded("placeholder-api")) {
- new SparkFabricPlaceholderApi(this.platform);
+ try {
+ SparkFabricPlaceholderApi.register(this.platform);
+ } catch (LinkageError e) {
+ // ignore
+ }
}
}
@@ -89,7 +102,6 @@ public class FabricServerSparkPlugin extends FabricSparkPlugin implements Comman
return 0;
}
- this.threadDumper.ensureSetup();
CommandOutput source = context.getSource().getEntity() != null ? context.getSource().getEntity() : context.getSource().getServer();
this.platform.executeCommand(new FabricCommandSender(source, this), args);
return Command.SINGLE_SUCCESS;
@@ -107,8 +119,15 @@ public class FabricServerSparkPlugin extends FabricSparkPlugin implements Comman
@Override
public boolean hasPermission(CommandOutput sender, String permission) {
- if (sender instanceof PlayerEntity) {
- return Permissions.check(((PlayerEntity) sender), permission, 4);
+ if (sender instanceof PlayerEntity player) {
+ return Permissions.getPermissionValue(player, permission).orElseGet(() -> {
+ MinecraftServer server = player.getServer();
+ if (server != null && server.isHost(player.getGameProfile())) {
+ return true;
+ }
+
+ return player.hasPermissionLevel(4);
+ });
} else {
return true;
}
@@ -123,6 +142,16 @@ public class FabricServerSparkPlugin extends FabricSparkPlugin implements Comman
}
@Override
+ public void executeSync(Runnable task) {
+ this.server.executeSync(task);
+ }
+
+ @Override
+ public ThreadDumper getDefaultThreadDumper() {
+ return this.gameThreadDumper;
+ }
+
+ @Override
public TickHook createTickHook() {
return new FabricTickHook.Server();
}
@@ -138,6 +167,21 @@ public class FabricServerSparkPlugin extends FabricSparkPlugin implements Comman
}
@Override
+ public ServerConfigProvider createServerConfigProvider() {
+ return new FabricServerConfigProvider();
+ }
+
+ @Override
+ public MetadataProvider createExtraMetadataProvider() {
+ return new FabricExtraMetadataProvider(this.server.getDataPackManager());
+ }
+
+ @Override
+ public WorldInfoProvider createWorldInfoProvider() {
+ return new FabricWorldInfoProvider.Server(this.server);
+ }
+
+ @Override
public PlatformInfo getPlatformInfo() {
return new FabricPlatformInfo(PlatformInfo.Type.SERVER);
}
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkMixinPlugin.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkMixinPlugin.java
new file mode 100644
index 0000000..cfc8c95
--- /dev/null
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkMixinPlugin.java
@@ -0,0 +1,71 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package me.lucko.spark.fabric.plugin;
+
+import me.lucko.spark.fabric.smap.SourceDebugCache;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.objectweb.asm.tree.ClassNode;
+import org.spongepowered.asm.mixin.MixinEnvironment;
+import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
+import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
+import org.spongepowered.asm.mixin.transformer.IMixinTransformer;
+import org.spongepowered.asm.mixin.transformer.ext.Extensions;
+import org.spongepowered.asm.mixin.transformer.ext.IExtension;
+import org.spongepowered.asm.mixin.transformer.ext.ITargetClassContext;
+
+import java.util.List;
+import java.util.Set;
+
+public class FabricSparkMixinPlugin implements IMixinConfigPlugin, IExtension {
+
+ private static final Logger LOGGER = LogManager.getLogger("spark");
+
+ @Override
+ public void onLoad(String mixinPackage) {
+ Object activeTransformer = MixinEnvironment.getCurrentEnvironment().getActiveTransformer();
+ if (activeTransformer instanceof IMixinTransformer transformer && transformer.getExtensions() instanceof Extensions extensions) {
+ extensions.add(this);
+ } else {
+ LOGGER.error(
+ "Failed to initialize SMAP parser for spark profiler. " +
+ "Mod information for mixin injected methods is now only available with the async-profiler engine."
+ );
+ }
+ }
+
+ @Override
+ public void export(MixinEnvironment env, String name, boolean force, ClassNode classNode) {
+ SourceDebugCache.put(name, classNode);
+ }
+
+ // noop
+ @Override public String getRefMapperConfig() { return null; }
+ @Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { return true; }
+ @Override public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) { }
+ @Override public List<String> getMixins() { return null; }
+ @Override public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { }
+ @Override public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { }
+ @Override public boolean checkActive(MixinEnvironment environment) { return true; }
+ @Override public void preApply(ITargetClassContext context) { }
+ @Override public void postApply(ITargetClassContext context) { }
+
+}
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java
index b1392d4..9a03b4e 100644
--- a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java
@@ -34,12 +34,14 @@ import com.mojang.brigadier.tree.LiteralCommandNode;
import me.lucko.spark.common.SparkPlatform;
import me.lucko.spark.common.SparkPlugin;
import me.lucko.spark.common.command.sender.CommandSender;
-import me.lucko.spark.common.sampler.ThreadDumper;
-import me.lucko.spark.common.util.ClassSourceLookup;
+import me.lucko.spark.common.sampler.source.ClassSourceLookup;
+import me.lucko.spark.common.sampler.source.SourceMetadata;
import me.lucko.spark.common.util.SparkThreadFactory;
import me.lucko.spark.fabric.FabricClassSourceLookup;
import me.lucko.spark.fabric.FabricSparkMod;
+import net.fabricmc.loader.api.FabricLoader;
+import net.fabricmc.loader.api.metadata.Person;
import net.minecraft.server.command.CommandOutput;
import org.apache.logging.log4j.LogManager;
@@ -47,10 +49,12 @@ import org.apache.logging.log4j.Logger;
import java.nio.file.Path;
import java.util.Arrays;
+import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Level;
+import java.util.stream.Collectors;
public abstract class FabricSparkPlugin implements SparkPlugin {
@@ -59,7 +63,6 @@ public abstract class FabricSparkPlugin implements SparkPlugin {
protected final ScheduledExecutorService scheduler;
protected SparkPlatform platform;
- protected final ThreadDumper.GameThread threadDumper = new ThreadDumper.GameThread();
protected FabricSparkPlugin(FabricSparkMod mod) {
this.mod = mod;
@@ -108,13 +111,20 @@ public abstract class FabricSparkPlugin implements SparkPlugin {
}
@Override
- public ThreadDumper getDefaultThreadDumper() {
- return this.threadDumper.get();
+ public ClassSourceLookup createClassSourceLookup() {
+ return new FabricClassSourceLookup();
}
@Override
- public ClassSourceLookup createClassSourceLookup() {
- return new FabricClassSourceLookup();
+ public Collection<SourceMetadata> getKnownSources() {
+ return SourceMetadata.gather(
+ FabricLoader.getInstance().getAllMods(),
+ mod -> mod.getMetadata().getId(),
+ mod -> mod.getMetadata().getVersion().getFriendlyString(),
+ mod -> mod.getMetadata().getAuthors().stream()
+ .map(Person::getName)
+ .collect(Collectors.joining(", "))
+ );
}
protected CompletableFuture<Suggestions> generateSuggestions(CommandSender sender, String[] args, SuggestionsBuilder builder) {
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/MixinUtils.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/MixinUtils.java
new file mode 100644
index 0000000..ebf2766
--- /dev/null
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/MixinUtils.java
@@ -0,0 +1,52 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package me.lucko.spark.fabric.smap;
+
+import org.spongepowered.asm.mixin.transformer.Config;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+public enum MixinUtils {
+ ;
+
+ private static final Map<String, Config> MIXIN_CONFIGS;
+
+ static {
+ Map<String, Config> configs;
+ try {
+ Field allConfigsField = Config.class.getDeclaredField("allConfigs");
+ allConfigsField.setAccessible(true);
+
+ //noinspection unchecked
+ configs = (Map<String, Config>) allConfigsField.get(null);
+ } catch (Exception e) {
+ e.printStackTrace();
+ configs = new HashMap<>();
+ }
+ MIXIN_CONFIGS = configs;
+ }
+
+ public static Map<String, Config> getMixinConfigs() {
+ return MIXIN_CONFIGS;
+ }
+}
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceDebugCache.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceDebugCache.java
new file mode 100644
index 0000000..88adae6
--- /dev/null
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceDebugCache.java
@@ -0,0 +1,87 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package me.lucko.spark.fabric.smap;
+
+import org.objectweb.asm.tree.ClassNode;
+import org.spongepowered.asm.service.IClassBytecodeProvider;
+import org.spongepowered.asm.service.MixinService;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Caches the lookup of class -> source debug info for classes loaded on the JVM.
+ *
+ * The {@link me.lucko.spark.fabric.plugin.FabricSparkMixinPlugin} also supplements this cache with
+ * extra information as classes are exported.
+ */
+public enum SourceDebugCache {
+ ;
+
+ // class name -> smap
+ private static final Map<String, SmapValue> CACHE = new ConcurrentHashMap<>();
+
+ public static void put(String className, ClassNode node) {
+ if (className == null || node == null) {
+ return;
+ }
+ className = className.replace('/', '.');
+ CACHE.put(className, SmapValue.of(node.sourceDebug));
+ }
+
+ public static String getSourceDebugInfo(String className) {
+ SmapValue cached = CACHE.get(className);
+ if (cached != null) {
+ return cached.value();
+ }
+
+ try {
+ IClassBytecodeProvider provider = MixinService.getService().getBytecodeProvider();
+ ClassNode classNode = provider.getClassNode(className.replace('.', '/'));
+
+ if (classNode != null) {
+ put(className, classNode);
+ return classNode.sourceDebug;
+ }
+
+ } catch (Exception e) {
+ // ignore
+ }
+
+ CACHE.put(className, SmapValue.NULL);
+ return null;
+ }
+
+ private record SmapValue(String value) {
+ static final SmapValue NULL = new SmapValue(null);
+
+ static SmapValue of(String value) {
+ if (value == null) {
+ return NULL;
+ } else {
+ return new SmapValue(value);
+ }
+ }
+
+ }
+
+}
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMap.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMap.java
new file mode 100644
index 0000000..5105a26
--- /dev/null
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMap.java
@@ -0,0 +1,133 @@
+/*
+ * SMAPSourceDebugExtension.java - Parse source debug extensions and
+ * enhance stack traces.
+ *
+ * Copyright (c) 2012 Michael Schierl
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND THE CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package me.lucko.spark.fabric.smap;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility class to parse "SMAP" (source map) information from loaded Java classes.
+ *
+ * @author <a href="https://stackoverflow.com/a/11299757">Michael Schierl</a>
+ */
+public class SourceMap {
+
+ private final String generatedFileName;
+ private final String firstStratum;
+ private final Map<Integer, FileInfo> fileinfo = new HashMap<>();
+ private final Map<Integer, int[]> reverseLineMapping = new HashMap<>();
+
+ private static final Pattern LINE_INFO_PATTERN = Pattern.compile("([0-9]+)(?:#([0-9]+))?(?:,([0-9]+))?:([0-9]+)(?:,([0-9]+))?");
+
+ public SourceMap(String value) {
+ String[] lines = value.split("\n");
+ if (!lines[0].equals("SMAP") || !lines[3].startsWith("*S ") || !lines[4].equals("*F")) {
+ throw new IllegalArgumentException(value);
+ }
+
+ this.generatedFileName = lines[1];
+ this.firstStratum = lines[3].substring(3);
+
+ int idx = 5;
+ while (!lines[idx].startsWith("*")) {
+ String infoline = lines[idx++];
+ String path = null;
+
+ if (infoline.startsWith("+ ")) {
+ path = lines[idx++];
+ infoline = infoline.substring(2);
+ }
+
+ int pos = infoline.indexOf(" ");
+ int filenum = Integer.parseInt(infoline.substring(0, pos));
+ String name = infoline.substring(pos + 1);
+
+ this.fileinfo.put(filenum, new FileInfo(name, path == null ? name : path));
+ }
+
+ if (lines[idx].equals("*L")) {
+ idx++;
+ int lastLFI = 0;
+
+ while (!lines[idx].startsWith("*")) {
+ Matcher m = LINE_INFO_PATTERN.matcher(lines[idx++]);
+ if (!m.matches()) {
+ throw new IllegalArgumentException(lines[idx - 1]);
+ }
+
+ int inputStartLine = Integer.parseInt(m.group(1));
+ int lineFileID = m.group(2) == null ? lastLFI : Integer.parseInt(m.group(2));
+ int repeatCount = m.group(3) == null ? 1 : Integer.parseInt(m.group(3));
+ int outputStartLine = Integer.parseInt(m.group(4));
+ int outputLineIncrement = m.group(5) == null ? 1 : Integer.parseInt(m.group(5));
+
+ for (int i = 0; i < repeatCount; i++) {
+ int[] inputMapping = new int[] { lineFileID, inputStartLine + i };
+ int baseOL = outputStartLine + i * outputLineIncrement;
+
+ for (int ol = baseOL; ol < baseOL + outputLineIncrement; ol++) {
+ if (!this.reverseLineMapping.containsKey(ol)) {
+ this.reverseLineMapping.put(ol, inputMapping);
+ }
+ }
+ }
+
+ lastLFI = lineFileID;
+ }
+ }
+ }
+
+ public String getGeneratedFileName() {
+ return this.generatedFileName;
+ }
+
+ public String getFirstStratum() {
+ return this.firstStratum;
+ }
+
+ public Map<Integer, FileInfo> getFileInfo() {
+ return this.fileinfo;
+ }
+
+ public Map<Integer, int[]> getReverseLineMapping() {
+ return this.reverseLineMapping;
+ }
+
+ public record FileInfo(String name, String path) { }
+} \ No newline at end of file
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMapProvider.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMapProvider.java
new file mode 100644
index 0000000..1a4f246
--- /dev/null
+++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMapProvider.java
@@ -0,0 +1,53 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package me.lucko.spark.fabric.smap;
+
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class SourceMapProvider {
+ private final Map<String, SourceMap> cache = new HashMap<>();
+
+ public @Nullable SourceMap getSourceMap(String className) {
+ if (this.cache.containsKey(className)) {
+ return this.cache.get(className);
+ }
+
+ SourceMap smap = null;
+ try {
+ String value = SourceDebugCache.getSourceDebugInfo(className);
+ if (value != null) {
+ value = value.replaceAll("\r\n?", "\n");
+ if (value.startsWith("SMAP\n")) {
+ smap = new SourceMap(value);
+ }
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+
+ this.cache.put(className, smap);
+ return smap;
+ }
+
+}
diff --git a/spark-fabric/src/main/resources/fabric.mod.json b/spark-fabric/src/main/resources/fabric.mod.json
index e2e600d..f1f0ad4 100644
--- a/spark-fabric/src/main/resources/fabric.mod.json
+++ b/spark-fabric/src/main/resources/fabric.mod.json
@@ -23,6 +23,9 @@
"me.lucko.spark.fabric.FabricSparkMod::initializeClient"
]
},
+ "mixins": [
+ "spark.mixins.json"
+ ],
"depends": {
"fabricloader": ">=0.4.0",
"fabric-api-base": "*",
diff --git a/spark-fabric/src/main/resources/spark.mixins.json b/spark-fabric/src/main/resources/spark.mixins.json
new file mode 100644
index 0000000..beaca2f
--- /dev/null
+++ b/spark-fabric/src/main/resources/spark.mixins.json
@@ -0,0 +1,15 @@
+{
+ "required": true,
+ "package": "me.lucko.spark.fabric.mixin",
+ "compatibilityLevel": "JAVA_17",
+ "client": [
+ "ClientEntityManagerAccessor",
+ "ClientWorldAccessor",
+ "MinecraftClientAccessor"
+ ],
+ "mixins": [
+ "ServerEntityManagerAccessor",
+ "ServerWorldAccessor"
+ ],
+ "plugin": "me.lucko.spark.fabric.plugin.FabricSparkMixinPlugin"
+} \ No newline at end of file