Skip to content

Commit

Permalink
Fix entity counts & chunk info with Moonrise on Fabric and NeoForge (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
jpenilla authored Aug 24, 2024
1 parent 46704df commit 7eff995
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@

package me.lucko.spark.fabric;

import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.lang.reflect.Method;
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 me.lucko.spark.fabric.mixin.WorldAccessor;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
Expand All @@ -40,30 +42,13 @@
import net.minecraft.world.GameRules;
import net.minecraft.world.entity.ClientEntityManager;
import net.minecraft.world.entity.EntityIndex;
import net.minecraft.world.entity.EntityTrackingSection;
import net.minecraft.world.entity.SectionedEntityCache;
import net.minecraft.world.entity.EntityLookup;

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;

Expand All @@ -78,10 +63,15 @@ public CountsResult pollCounts() {
int chunks = 0;

for (ServerWorld world : this.server.getWorlds()) {
ServerEntityManager<Entity> entityManager = ((ServerWorldAccessor) world).getEntityManager();
EntityIndex<?> entityIndex = ((ServerEntityManagerAccessor) entityManager).getIndex();

entities += entityIndex.size();
if (FabricLoader.getInstance().isModLoaded("moonrise")) {
entities += MoonriseMethods.getEntityCount(((WorldAccessor) world).spark$getEntityLookup());
} else {
ServerEntityManager<Entity> entityManager = ((ServerWorldAccessor) world).getEntityManager();
EntityIndex<?> entityIndex = ((ServerEntityManagerAccessor) entityManager).getIndex();
entities += entityIndex.size();
}

chunks += world.getChunkManager().getLoadedChunkCount();
}

Expand All @@ -93,11 +83,15 @@ 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();
Long2ObjectOpenHashMap<FabricChunkInfo> worldInfos = new Long2ObjectOpenHashMap<>();

for (Entity entity : ((WorldAccessor) world).spark$getEntityLookup().iterate()) {
FabricChunkInfo info = worldInfos.computeIfAbsent(
entity.getChunkPos().toLong(), FabricChunkInfo::new);
info.entityCounts.increment(entity.getType());
}

List<FabricChunkInfo> list = getChunksFromCache(cache);
data.put(world.getRegistryKey().getValue().getPath(), list);
data.put(world.getRegistryKey().getValue().getPath(), List.copyOf(worldInfos.values()));
}

return data;
Expand Down Expand Up @@ -140,10 +134,16 @@ public CountsResult pollCounts() {
return null;
}

ClientEntityManager<Entity> entityManager = ((ClientWorldAccessor) world).getEntityManager();
EntityIndex<?> entityIndex = ((ClientEntityManagerAccessor) entityManager).getIndex();
int entities;

if (FabricLoader.getInstance().isModLoaded("moonrise")) {
entities = MoonriseMethods.getEntityCount(((WorldAccessor) world).spark$getEntityLookup());
} else {
ClientEntityManager<Entity> entityManager = ((ClientWorldAccessor) world).getEntityManager();
EntityIndex<?> entityIndex = ((ClientEntityManagerAccessor) entityManager).getIndex();
entities = entityIndex.size();
}

int entities = entityIndex.size();
int chunks = world.getChunkManager().getLoadedChunkCount();

return new CountsResult(-1, entities, -1, chunks);
Expand All @@ -158,11 +158,14 @@ public ChunksResult<FabricChunkInfo> pollChunks() {

ChunksResult<FabricChunkInfo> data = new ChunksResult<>();

ClientEntityManager<Entity> entityManager = ((ClientWorldAccessor) world).getEntityManager();
SectionedEntityCache<Entity> cache = ((ClientEntityManagerAccessor) entityManager).getCache();
Long2ObjectOpenHashMap<FabricChunkInfo> worldInfos = new Long2ObjectOpenHashMap<>();

List<FabricChunkInfo> list = getChunksFromCache(cache);
data.put(world.getRegistryKey().getValue().getPath(), list);
for (Entity entity : ((WorldAccessor) world).spark$getEntityLookup().iterate()) {
FabricChunkInfo info = worldInfos.computeIfAbsent(entity.getChunkPos().toLong(), FabricChunkInfo::new);
info.entityCounts.increment(entity.getType());
}

data.put(world.getRegistryKey().getValue().getPath(), List.copyOf(worldInfos.values()));

return data;
}
Expand Down Expand Up @@ -197,17 +200,10 @@ public <T extends GameRules.Rule<T>> void visit(GameRules.Key<T> key, GameRules.
static final class FabricChunkInfo extends AbstractChunkInfo<EntityType<?>> {
private final CountMap<EntityType<?>> entityCounts;

FabricChunkInfo(long chunkPos, Stream<EntityTrackingSection<Entity>> entities) {
FabricChunkInfo(long chunkPos) {
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
Expand All @@ -221,5 +217,28 @@ public String entityTypeName(EntityType<?> type) {
}
}

private static final class MoonriseMethods {
private static Method getEntityCount;

private static Method getEntityCountMethod(EntityLookup<Entity> getter) {
if (getEntityCount == null) {
try {
getEntityCount = getter.getClass().getMethod("getEntityCount");
} catch (final ReflectiveOperationException e) {
throw new RuntimeException("Cannot find Moonrise getEntityCount method", e);
}
}
return getEntityCount;
}

private static int getEntityCount(EntityLookup<Entity> getter) {
try {
return (int) getEntityCountMethod(getter).invoke(getter);
} catch (final ReflectiveOperationException e) {
throw new RuntimeException("Failed to invoke Moonrise getEntityCount method", e);
}
}
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* This file is part of spark.
*
* Copyright (c) lucko (Luck) <[email protected]>
* 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.world.World;
import net.minecraft.world.entity.EntityLookup;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;

@Mixin(World.class)
public interface WorldAccessor {

@Invoker(value = "getEntityLookup")
EntityLookup<Entity> spark$getEntityLookup();

}
3 changes: 2 additions & 1 deletion spark-fabric/src/main/resources/spark.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
],
"mixins": [
"ServerEntityManagerAccessor",
"ServerWorldAccessor"
"ServerWorldAccessor",
"WorldAccessor"
],
"plugin": "me.lucko.spark.fabric.plugin.FabricSparkMixinPlugin"
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@

package me.lucko.spark.forge;

import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import me.lucko.spark.common.platform.world.AbstractChunkInfo;
import me.lucko.spark.common.platform.world.CountMap;
import me.lucko.spark.common.platform.world.WorldInfoProvider;
Expand All @@ -34,32 +33,14 @@
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.entity.EntityLookup;
import net.minecraft.world.level.entity.EntitySection;
import net.minecraft.world.level.entity.EntitySectionStorage;
import net.minecraft.world.level.entity.PersistentEntitySectionManager;
import net.minecraft.world.level.entity.TransientEntitySectionManager;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Stream;

public abstract class ForgeWorldInfoProvider implements WorldInfoProvider {

protected List<ForgeChunkInfo> getChunksFromCache(EntitySectionStorage<Entity> cache) {
LongSet loadedChunks = cache.getAllChunksWithExistingSections();
List<ForgeChunkInfo> list = new ArrayList<>(loadedChunks.size());

for (LongIterator iterator = loadedChunks.iterator(); iterator.hasNext(); ) {
long chunkPos = iterator.nextLong();
Stream<EntitySection<Entity>> sections = cache.getExistingSectionsInChunk(chunkPos);

list.add(new ForgeChunkInfo(chunkPos, sections));
}

return list;
}

public static final class Server extends ForgeWorldInfoProvider {
private final MinecraftServer server;

Expand Down Expand Up @@ -89,11 +70,15 @@ public ChunksResult<ForgeChunkInfo> pollChunks() {
ChunksResult<ForgeChunkInfo> data = new ChunksResult<>();

for (ServerLevel level : this.server.getAllLevels()) {
PersistentEntitySectionManager<Entity> entityManager = level.entityManager;
EntitySectionStorage<Entity> cache = entityManager.sectionStorage;
Long2ObjectOpenHashMap<ForgeChunkInfo> levelInfos = new Long2ObjectOpenHashMap<>();

List<ForgeChunkInfo> list = getChunksFromCache(cache);
data.put(level.dimension().location().getPath(), list);
for (Entity entity : level.getEntities().getAll()) {
ForgeChunkInfo info = levelInfos.computeIfAbsent(
entity.chunkPosition().toLong(), ForgeChunkInfo::new);
info.entityCounts.increment(entity.getType());
}

data.put(level.dimension().location().getPath(), List.copyOf(levelInfos.values()));
}

return data;
Expand Down Expand Up @@ -155,11 +140,14 @@ public ChunksResult<ForgeChunkInfo> pollChunks() {

ChunksResult<ForgeChunkInfo> data = new ChunksResult<>();

TransientEntitySectionManager<Entity> entityManager = level.entityStorage;
EntitySectionStorage<Entity> cache = entityManager.sectionStorage;
Long2ObjectOpenHashMap<ForgeChunkInfo> levelInfos = new Long2ObjectOpenHashMap<>();

List<ForgeChunkInfo> list = getChunksFromCache(cache);
data.put(level.dimension().location().getPath(), list);
for (Entity entity : level.getEntities().getAll()) {
ForgeChunkInfo info = levelInfos.computeIfAbsent(entity.chunkPosition().toLong(), ForgeChunkInfo::new);
info.entityCounts.increment(entity.getType());
}

data.put(level.dimension().location().getPath(), List.copyOf(levelInfos.values()));

return data;
}
Expand Down Expand Up @@ -191,20 +179,13 @@ public <T extends GameRules.Value<T>> void visit(GameRules.Key<T> key, GameRules
}
}

static final class ForgeChunkInfo extends AbstractChunkInfo<EntityType<?>> {
public static final class ForgeChunkInfo extends AbstractChunkInfo<EntityType<?>> {
private final CountMap<EntityType<?>> entityCounts;

ForgeChunkInfo(long chunkPos, Stream<EntitySection<Entity>> entities) {
ForgeChunkInfo(long chunkPos) {
super(ChunkPos.getX(chunkPos), ChunkPos.getZ(chunkPos));

this.entityCounts = new CountMap.Simple<>(new HashMap<>());
entities.forEach(section -> {
if (section.getStatus().isAccessible()) {
section.getEntities().forEach(entity ->
this.entityCounts.increment(entity.getType())
);
}
});
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ public net.minecraft.client.multiplayer.ClientLevel f_171631_ # entityStorage
public net.minecraft.world.level.entity.TransientEntitySectionManager f_157638_ # sectionStorage
public net.minecraft.world.level.entity.TransientEntitySectionManager f_157637_ # entityStorage
public net.minecraft.client.Minecraft f_91018_ # gameThread
public net.minecraft.client.multiplayer.ClientLevel m_142646_()Lnet/minecraft/world/level/entity/LevelEntityGetter; # getEntities
Loading

0 comments on commit 7eff995

Please sign in to comment.