Add SyncDataPackContents and TagsLoaded lifecycle events (#2265)

* Add SyncDataPackContents and TagsLoaded lifecycle events

* tagsLoaded -> onTagsLoaded, isClient -> client

* Compile error in testmod

* Make SyncDataPackContents fire for every player

* syncDataPackContents -> onSyncDataPackContents

(cherry picked from commit cc71601c72)
This commit is contained in:
modmuss50 2022-06-13 19:38:54 +01:00
parent e1dbfca1b3
commit 33fbc73844
18 changed files with 172 additions and 149 deletions

View file

@ -1,11 +0,0 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.content.registry.client",
"compatibilityLevel": "JAVA_16",
"client": [
"MixinClientPlayNetworkHandler"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -31,7 +31,7 @@ import net.minecraft.item.Item;
import net.minecraft.item.ItemConvertible;
import net.minecraft.util.registry.Registry;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.CommonLifecycleEvents;
import net.fabricmc.fabric.api.registry.FuelRegistry;
// TODO: Clamp values to 32767 (+ add hook for mods which extend the limit to disable the check?)
@ -42,10 +42,9 @@ public final class FuelRegistryImpl implements FuelRegistry {
private volatile Map<Item, Integer> fuelTimeCache = null; // thread safe via copy-on-write mechanism
public FuelRegistryImpl() {
ServerLifecycleEvents.END_DATA_PACK_RELOAD.register((server, serverResourceManager, success) -> {
if (success) {
resetCache();
}
// Reset cache after tags change since it depends on tags.
CommonLifecycleEvents.TAGS_LOADED.register((registries, client) -> {
resetCache();
});
}

View file

@ -23,11 +23,7 @@
},
"description": "Adds registries for vanilla mechanics that are missing them.",
"mixins": [
"fabric-content-registries-v0.mixins.json",
{
"config": "fabric-content-registries-v0.client.mixins.json",
"environment": "client"
}
"fabric-content-registries-v0.mixins.json"
],
"accessWidener" : "fabric-content-registries-v0.accesswidener",
"custom": {

View file

@ -28,12 +28,15 @@ import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket;
import net.minecraft.network.packet.s2c.play.PlayerRespawnS2CPacket;
import net.minecraft.network.packet.s2c.play.SynchronizeTagsS2CPacket;
import net.minecraft.util.registry.DynamicRegistryManager;
import net.minecraft.world.chunk.WorldChunk;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientBlockEntityEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.CommonLifecycleEvents;
import net.fabricmc.fabric.impl.event.lifecycle.LoadedChunksCache;
@Environment(EnvType.CLIENT)
@ -41,6 +44,8 @@ import net.fabricmc.fabric.impl.event.lifecycle.LoadedChunksCache;
abstract class ClientPlayNetworkHandlerMixin {
@Shadow
private ClientWorld world;
@Shadow
private DynamicRegistryManager.Immutable registryManager;
@Inject(method = "onPlayerRespawn", at = @At(value = "NEW", target = "net/minecraft/client/world/ClientWorld"))
private void onPlayerRespawn(PlayerRespawnS2CPacket packet, CallbackInfo ci) {
@ -96,4 +101,16 @@ abstract class ClientPlayNetworkHandlerMixin {
}
}
}
@Inject(
method = "onSynchronizeTags",
at = @At(
value = "INVOKE",
target = "java/util/Map.forEach(Ljava/util/function/BiConsumer;)V",
shift = At.Shift.AFTER, by = 1
)
)
private void hookOnSynchronizeTags(SynchronizeTagsS2CPacket packet, CallbackInfo ci) {
CommonLifecycleEvents.TAGS_LOADED.invoker().onTagsLoaded(registryManager, true);
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.event.lifecycle.v1;
import net.minecraft.util.registry.DynamicRegistryManager;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
public class CommonLifecycleEvents {
private CommonLifecycleEvents() {
}
/**
* Called when tags are loaded or updated.
*/
public static final Event<TagsLoaded> TAGS_LOADED = EventFactory.createArrayBacked(TagsLoaded.class, callbacks -> (registries, client) -> {
for (TagsLoaded callback : callbacks) {
callback.onTagsLoaded(registries, client);
}
});
public interface TagsLoaded {
/**
* @param registries Up-to-date registries from which the tags can be retrieved.
* @param client True if the client just received a sync packet, false if the server just (re)loaded the tags.
*/
void onTagsLoaded(DynamicRegistryManager registries, boolean client);
}
}

View file

@ -19,6 +19,7 @@ package net.fabricmc.fabric.api.event.lifecycle.v1;
import net.minecraft.resource.LifecycledResourceManager;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ServerPlayerEntity;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
@ -76,6 +77,16 @@ public final class ServerLifecycleEvents {
}
});
/**
* Called when a Minecraft server is about to send tag and recipe data to a player.
* @see SyncDataPackContents
*/
public static final Event<SyncDataPackContents> SYNC_DATA_PACK_CONTENTS = EventFactory.createArrayBacked(SyncDataPackContents.class, callbacks -> (player, joined) -> {
for (SyncDataPackContents callback : callbacks) {
callback.onSyncDataPackContents(player, joined);
}
});
/**
* Called before a Minecraft server reloads data packs.
*/
@ -116,6 +127,21 @@ public final class ServerLifecycleEvents {
void onServerStopped(MinecraftServer server);
}
@FunctionalInterface
public interface SyncDataPackContents {
/**
* Called right before tags and recipes are sent to a player,
* either because the player joined, or because the server reloaded resources.
* The {@linkplain MinecraftServer#getResourceManager() server resource manager} is up-to-date when this is called.
*
* <p>For example, this event can be used to sync data loaded with custom resource reloaders.
*
* @param player Player to which the data is being sent.
* @param joined True if the player is joining the server, false if the server finished a successful resource reload.
*/
void onSyncDataPackContents(ServerPlayerEntity player, boolean joined);
}
@FunctionalInterface
public interface StartDataPackReload {
void startDataPackReload(MinecraftServer server, LifecycledResourceManager resourceManager);

View file

@ -14,23 +14,22 @@
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.content.registry.client;
package net.fabricmc.fabric.mixin.event.lifecycle;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.network.packet.s2c.play.SynchronizeTagsS2CPacket;
import net.minecraft.server.DataPackContents;
import net.minecraft.util.registry.DynamicRegistryManager;
import net.fabricmc.fabric.api.registry.FuelRegistry;
import net.fabricmc.fabric.impl.content.registry.FuelRegistryImpl;
import net.fabricmc.fabric.api.event.lifecycle.v1.CommonLifecycleEvents;
@Mixin(ClientPlayNetworkHandler.class)
public abstract class MixinClientPlayNetworkHandler {
@Inject(at = @At("TAIL"), method = "onSynchronizeTags")
private void onSynchronizeTagsHook(SynchronizeTagsS2CPacket packet, CallbackInfo info) {
((FuelRegistryImpl) FuelRegistry.INSTANCE).resetCache();
@Mixin(DataPackContents.class)
public class DataPackContentsMixin {
@Inject(method = "refresh", at = @At("TAIL"))
private void hookRefresh(DynamicRegistryManager registries, CallbackInfo ci) {
CommonLifecycleEvents.TAGS_LOADED.invoker().onTagsLoaded(registries, false);
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.event.lifecycle;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.network.ClientConnection;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ServerPlayerEntity;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
@Mixin(PlayerManager.class)
public class PlayerManagerMixin {
@Inject(
method = "onPlayerConnect",
at = @At(value = "INVOKE", target = "net/minecraft/network/packet/s2c/play/SynchronizeRecipesS2CPacket.<init>(Ljava/util/Collection;)V")
)
private void hookOnPlayerConnect(ClientConnection connection, ServerPlayerEntity player, CallbackInfo ci) {
ServerLifecycleEvents.SYNC_DATA_PACK_CONTENTS.invoker().onSyncDataPackContents(player, true);
}
@Inject(
method = "onDataPacksReloaded",
at = @At(value = "INVOKE", target = "net/minecraft/network/packet/s2c/play/SynchronizeTagsS2CPacket.<init>(Ljava/util/Map;)V")
)
private void hookOnDataPacksReloaded(CallbackInfo ci) {
for (ServerPlayerEntity player : ((PlayerManager) (Object) this).getPlayerList()) {
ServerLifecycleEvents.SYNC_DATA_PACK_CONTENTS.invoker().onSyncDataPackContents(player, false);
}
}
}

View file

@ -3,7 +3,9 @@
"package": "net.fabricmc.fabric.mixin.event.lifecycle",
"compatibilityLevel": "JAVA_16",
"mixins": [
"DataPackContentsMixin",
"MinecraftServerMixin",
"PlayerManagerMixin",
"ServerWorldEntityLoaderMixin",
"ServerWorldMixin",
"ThreadedAnvilChunkStorageMixin",

View file

@ -14,16 +14,16 @@
* limitations under the License.
*/
package net.fabricmc.fabric.impl.mininglevel;
import net.minecraft.resource.ResourceType;
package net.fabricmc.fabric.test.event.lifecycle;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.fabricmc.fabric.api.event.lifecycle.v1.CommonLifecycleEvents;
public final class FabricMiningLevelInit implements ModInitializer {
public class CommonLifecycleTests implements ModInitializer {
@Override
public void onInitialize() {
ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(new MiningLevelCacheInvalidator());
CommonLifecycleEvents.TAGS_LOADED.register((registries, client) -> {
ServerLifecycleTests.LOGGER.info("Tags (re)loaded on {} {}", client ? "client" : "server", Thread.currentThread());
});
}
}

View file

@ -50,5 +50,9 @@ public final class ServerLifecycleTests implements ModInitializer {
ServerWorldEvents.UNLOAD.register((server, world) -> {
LOGGER.info("Unloaded world " + world.getRegistryKey().getValue().toString());
});
ServerLifecycleEvents.SYNC_DATA_PACK_CONTENTS.register((player, joined) -> {
LOGGER.info("SyncDataPackContents received for {}", joined ? "join" : "reload");
});
}
}

View file

@ -10,6 +10,7 @@
},
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.event.lifecycle.CommonLifecycleTests",
"net.fabricmc.fabric.test.event.lifecycle.ServerBlockEntityLifecycleTests",
"net.fabricmc.fabric.test.event.lifecycle.ServerEntityLifecycleTests",
"net.fabricmc.fabric.test.event.lifecycle.ServerLifecycleTests",

View file

@ -3,6 +3,7 @@ version = getSubprojectVersion(project)
moduleDependencies(project, [
'fabric-api-base',
'fabric-lifecycle-events-v1',
'fabric-resource-loader-v0'
])

View file

@ -1,37 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.mininglevel.client;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Slice;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.network.packet.s2c.play.SynchronizeTagsS2CPacket;
import net.fabricmc.fabric.impl.mininglevel.MiningLevelManagerImpl;
@Mixin(ClientPlayNetworkHandler.class)
abstract class ClientPlayNetworkHandlerMixin {
@Inject(method = "onSynchronizeTags", at = @At("RETURN"), slice = @Slice(from = @At(value = "FIELD", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;tagManager:Lnet/minecraft/tag/TagManager;", opcode = Opcodes.PUTFIELD)))
private void fabric$clearMiningLevelCache(SynchronizeTagsS2CPacket packet, CallbackInfo info) {
MiningLevelManagerImpl.clearCache();
}
}

View file

@ -1,11 +0,0 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.mininglevel.client",
"compatibilityLevel": "JAVA_16",
"client": [
"ClientPlayNetworkHandlerMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -1,47 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.mininglevel;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.resource.ResourceReloadListenerKeys;
import net.fabricmc.fabric.api.resource.SimpleSynchronousResourceReloadListener;
final class MiningLevelCacheInvalidator implements SimpleSynchronousResourceReloadListener {
private static final Identifier ID = new Identifier("fabric-mining-level-api-v1", "cache_invalidator");
private static final Set<Identifier> DEPENDENCIES = Collections.singleton(ResourceReloadListenerKeys.TAGS);
@Override
public Identifier getFabricId() {
return ID;
}
@Override
public Collection<Identifier> getFabricDependencies() {
return DEPENDENCIES;
}
@Override
public void reload(ResourceManager manager) {
MiningLevelManagerImpl.clearCache();
}
}

View file

@ -29,6 +29,7 @@ import net.minecraft.block.BlockState;
import net.minecraft.tag.BlockTags;
import net.minecraft.tag.TagKey;
import net.fabricmc.fabric.api.event.lifecycle.v1.CommonLifecycleEvents;
import net.fabricmc.yarn.constants.MiningLevels;
public final class MiningLevelManagerImpl {
@ -36,11 +37,13 @@ public final class MiningLevelManagerImpl {
private static final String TOOL_TAG_NAMESPACE = "fabric";
private static final Pattern TOOL_TAG_PATTERN = Pattern.compile("^needs_tool_level_([0-9]+)$");
// A cache of block state mining levels. Cleared by
// - MiningLevelCacheInvalidator when tags are reloaded
// - ClientPlayNetworkHandlerMixin when tags are synced
// A cache of block state mining levels. Cleared when tags are updated.
private static final ThreadLocal<Reference2IntMap<BlockState>> CACHE = ThreadLocal.withInitial(Reference2IntOpenHashMap::new);
static {
CommonLifecycleEvents.TAGS_LOADED.register((registries, client) -> CACHE.get().clear());
}
public static int getRequiredMiningLevel(BlockState state) {
return CACHE.get().computeIfAbsent(state, s -> {
int miningLevel = MiningLevels.HAND;
@ -75,8 +78,4 @@ public final class MiningLevelManagerImpl {
return miningLevel;
});
}
public static void clearCache() {
CACHE.get().clear();
}
}

View file

@ -15,23 +15,15 @@
"authors": [
"FabricMC"
],
"entrypoints": {
"main": [
"net.fabricmc.fabric.impl.mininglevel.FabricMiningLevelInit"
]
},
"depends": {
"fabricloader": ">=0.4.0",
"fabric-api-base": "*",
"fabric-lifecycle-events-v1": "*",
"fabric-resource-loader-v0": "*"
},
"description": "Adds support for custom mining levels.",
"mixins": [
"fabric-mining-level-api-v1.mixins.json",
{
"config": "fabric-mining-level-api-v1.client.mixins.json",
"environment": "client"
}
"fabric-mining-level-api-v1.mixins.json"
],
"custom": {
"fabric-api:module-lifecycle": "stable"