Lifecycle Events V1 [1.15] ()

* Lifecycle Events V1

Now includes Chunk and (Block)Entity (un)load events

* Add some tests to verify worlds are ticking in the log

* Lambda boogaloo

* Add some docs. Distinguish between a server starting to stop and server which has stopped.

* Split up test mods, some tweaks to (block)entity (un)load events.

Bind the ServerWorld being closed during shutdown to unload (block)entities.

* Shift around a few profiler variables and finalize

* Complete the tests, Block entities on server should be reliably tracked now.

Entities on the server obviously still need to be wrangled.

* Drop Server Entity Unload callback.

Believe me, this was a hard decision but it stands on the fact that about 20-40% of entities silently unload without going through the proper "unloadEntity" method in ServerWorld. No amount of debug hacks, double tracking unload events and even replacing the entity maps do not fix this issue. So I have decided to drop this from the feature set.

* checkstyle

* generic-events -> item-api

* Server start -> Server started

* Allow getting current server from Lifecycle

People have asked for this, but it is not encouraged for obvious reasons. Should be staged well enough to revert if we decide to.

* checkstyle lol

* update injection name

* Checkstyle lol: Redundant modifiers

* Add client starting, stopping and stopped callbacks.

* Loicenses

* Reorganize so each event category has it's own class.

Also this collapses the pretty widely reaching interfaces into more specific inner classes to avoid issues with generics.

* Some docs and slight name changes

* Add start tick callbacks to worlds, server and client

* Enhance some client related docs to life cycle

* Deprecate for reasons of discouraging singletonish server getter methods in lifecycle

* Add some description related to integrated server on server stopping.

* Add small test to verify tick starts are right spot of load

* Docs and a tiny bit more testing.

* Try clarifying client docs

* Drop a slightly unnessecary event

* Actually call and implement START_SERVER_TICK event

* Remove non-existent test

* again

* Refer to minecraft itself in client lifecycle docs

* Refer to Minecraft itself within ServerLifecycleEvents

* Remove primary server getters

* IJ DO YOU SPEAK RESOLVING IMPORTS

(cherry picked from commit c9257e8a11d8361469349f4171263121bb111af7)

* Prune the tests that shouldn't exist

* Listen here checkstyle you bugger

(cherry picked from commit 9701bba4002cec089c9d3738b1f226128078c130)

* Split up events to individual interfaces. Make Chunk events use WorldChunk instead.
This commit is contained in:
i509VCB 2020-06-25 14:28:49 -07:00 committed by GitHub
parent b2771bdedd
commit 2f23104bdd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 2428 additions and 142 deletions
fabric-events-lifecycle-v0
fabric-item-api-v1
build.gradle
src
main
java/net/fabricmc/fabric
api/client/item/v1
mixin/item/client
resources
testmod
java/net/fabricmc/fabric/test/item/client
resources
fabric-lifecycle-events-v1
settings.gradle

View file

@ -1,6 +1,8 @@
archivesBaseName = "fabric-events-lifecycle-v0"
version = getSubprojectVersion(project, "0.1.2")
version = getSubprojectVersion(project, "0.2.0")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')
compile project(path: ':fabric-item-api-v1', configuration: 'dev')
compile project(path: ':fabric-lifecycle-events-v1', configuration: 'dev')
}

View file

@ -18,10 +18,16 @@ package net.fabricmc.fabric.api.event.client;
import net.minecraft.client.MinecraftClient;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
@Deprecated
public interface ClientTickCallback {
/**
* @deprecated Please use {@link ClientTickEvents#END_CLIENT_TICK}.
*/
@Deprecated
Event<ClientTickCallback> EVENT = EventFactory.createArrayBacked(ClientTickCallback.class,
(listeners) -> {
if (EventFactory.isProfilingEnabled()) {

View file

@ -25,10 +25,15 @@ import net.minecraft.text.Text;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
/**
* @deprecated Please use {@link net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback}
*/
@Deprecated
public interface ItemTooltipCallback {
/**
* Fired after the game has appended all base tooltip lines to the list.
*/
@Deprecated
Event<ItemTooltipCallback> EVENT = EventFactory.createArrayBacked(ItemTooltipCallback.class, (listeners) -> (stack, tooltipContext, lines) -> {
for (ItemTooltipCallback callback : listeners) {
callback.getTooltip(stack, tooltipContext, lines);

View file

@ -21,7 +21,12 @@ import net.minecraft.server.MinecraftServer;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
@Deprecated
public interface ServerStartCallback {
/**
* @deprecated Please use {@link net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents#SERVER_STARTED}
*/
@Deprecated
Event<ServerStartCallback> EVENT = EventFactory.createArrayBacked(ServerStartCallback.class,
(listeners) -> (server) -> {
for (ServerStartCallback event : listeners) {

View file

@ -21,7 +21,12 @@ import net.minecraft.server.MinecraftServer;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
@Deprecated
public interface ServerStopCallback {
/**
* @deprecated Please use {@link net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents#SERVER_STOPPING}
*/
@Deprecated
Event<ServerStopCallback> EVENT = EventFactory.createArrayBacked(ServerStopCallback.class,
(listeners) -> (server) -> {
for (ServerStopCallback event : listeners) {

View file

@ -20,8 +20,14 @@ import net.minecraft.server.MinecraftServer;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
@Deprecated
public interface ServerTickCallback {
/**
* @deprecated Please use {@link ServerTickEvents#END_SERVER_TICK}
*/
@Deprecated
Event<ServerTickCallback> EVENT = EventFactory.createArrayBacked(ServerTickCallback.class,
(listeners) -> {
if (EventFactory.isProfilingEnabled()) {

View file

@ -18,10 +18,18 @@ package net.fabricmc.fabric.api.event.world;
import net.minecraft.world.World;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
@Deprecated
public interface WorldTickCallback {
/**
* @deprecated The new WorldTickCallback has been split into a client and server callback.
* Please use the {@link ServerTickEvents#END_WORLD_TICK server} or {@link ClientTickEvents#END_WORLD_TICK client} callbacks.
*/
@Deprecated
Event<WorldTickCallback> EVENT = EventFactory.createArrayBacked(WorldTickCallback.class,
(listeners) -> {
if (EventFactory.isProfilingEnabled()) {

View file

@ -0,0 +1,37 @@
/*
* 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.event.lifecycle;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.event.server.ServerStartCallback;
import net.fabricmc.fabric.api.event.server.ServerStopCallback;
import net.fabricmc.fabric.api.event.server.ServerTickCallback;
import net.fabricmc.fabric.api.event.world.WorldTickCallback;
public class LegacyEventInvokers implements ModInitializer {
@Override
public void onInitialize() {
// Allows deprecated events to still be invoked by the newer implementations
ServerLifecycleEvents.SERVER_STARTED.register(server -> ServerStartCallback.EVENT.invoker().onStartServer(server));
ServerLifecycleEvents.SERVER_STOPPING.register(server -> ServerStopCallback.EVENT.invoker().onStopServer(server));
ServerTickEvents.END_SERVER_TICK.register(server -> ServerTickCallback.EVENT.invoker().tick(server));
// Tick old events on ServerWorld
ServerTickEvents.END_WORLD_TICK.register(world -> WorldTickCallback.EVENT.invoker().tick(world));
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.event.lifecycle.client;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.event.client.ClientTickCallback;
import net.fabricmc.fabric.api.event.client.ItemTooltipCallback;
import net.fabricmc.fabric.api.event.world.WorldTickCallback;
public class LegacyClientEventInvokers implements ClientModInitializer {
@Override
public void onInitializeClient() {
// Allows deprecated events to still be invoked by the newer implementations
ClientTickEvents.END_CLIENT_TICK.register(client -> ClientTickCallback.EVENT.invoker().tick(client));
// Tick old events on ClientWorld
ClientTickEvents.END_WORLD_TICK.register(world -> WorldTickCallback.EVENT.invoker().tick(world));
// This is part of item api now.
net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback.EVENT.register((stack, context, lines) -> ItemTooltipCallback.EVENT.invoker().getTooltip(stack, context, lines));
}
}

View file

@ -1,34 +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.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.MinecraftClient;
import net.fabricmc.fabric.api.event.client.ClientTickCallback;
@Mixin(MinecraftClient.class)
public class MixinMinecraftClient {
@Inject(at = @At("RETURN"), method = "tick")
public void tick(CallbackInfo info) {
ClientTickCallback.EVENT.invoker().tick((MinecraftClient) (Object) this);
}
}

View file

@ -1,48 +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.event.lifecycle;
import java.util.function.BooleanSupplier;
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.server.MinecraftServer;
import net.fabricmc.fabric.api.event.server.ServerStartCallback;
import net.fabricmc.fabric.api.event.server.ServerStopCallback;
import net.fabricmc.fabric.api.event.server.ServerTickCallback;
@Mixin(MinecraftServer.class)
public class MixinMinecraftServer {
@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;setFavicon(Lnet/minecraft/server/ServerMetadata;)V", ordinal = 0), method = "run")
public void afterSetupServer(CallbackInfo info) {
ServerStartCallback.EVENT.invoker().onStartServer((MinecraftServer) (Object) this);
}
@Inject(at = @At("HEAD"), method = "shutdown")
public void beforeShutdownServer(CallbackInfo info) {
ServerStopCallback.EVENT.invoker().onStopServer((MinecraftServer) (Object) this);
}
@Inject(at = @At("RETURN"), method = "tick")
protected void tick(BooleanSupplier var1, CallbackInfo info) {
ServerTickCallback.EVENT.invoker().tick((MinecraftServer) (Object) this);
}
}

View file

@ -1,35 +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.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.world.World;
import net.fabricmc.fabric.api.event.world.WorldTickCallback;
@Mixin(World.class)
public class MixinWorld {
// TODO split into ClientWorld/ServerWorld ticks? mmm need more mappings
@Inject(at = @At("RETURN"), method = "tickBlockEntities")
public void tickBlockEntitiesAfter(CallbackInfo info) {
WorldTickCallback.EVENT.invoker().tick((World) (Object) this);
}
}

View file

@ -1,16 +0,0 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.event.lifecycle",
"compatibilityLevel": "JAVA_8",
"mixins": [
"MixinMinecraftServer",
"MixinWorld"
],
"client": [
"MixinItemStack",
"MixinMinecraftClient"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -15,12 +15,19 @@
"authors": [
"FabricMC"
],
"entrypoints": {
"main": [
"net.fabricmc.fabric.impl.event.lifecycle.LegacyEventInvokers"
],
"client": [
"net.fabricmc.fabric.impl.event.lifecycle.client.LegacyClientEventInvokers"
]
},
"depends": {
"fabricloader": ">=0.4.0",
"fabric-api-base": "*"
"fabric-api-base": "*",
"fabric-item-api-v1": "*",
"fabric-lifecycle-events-v1": "*"
},
"description": "Events for the game's lifecycle.",
"mixins": [
"fabric-events-lifecycle-v0.mixins.json"
]
"description": "Legacy events for the game's lifecycle, superseded by fabric-lifecycle-events-v1 and fabric-item-api-v1."
}

View file

@ -0,0 +1,63 @@
/*
* 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.test.event.lifecycle.legacy;
import java.util.HashMap;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.minecraft.world.dimension.DimensionType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.server.ServerStartCallback;
import net.fabricmc.fabric.api.event.server.ServerStopCallback;
import net.fabricmc.fabric.api.event.server.ServerTickCallback;
import net.fabricmc.fabric.api.event.world.WorldTickCallback;
public class LegacyLifecycleEventsTest implements ModInitializer {
public static final Logger LOGGER = LogManager.getLogger("LegacyLifecycleEventsTest");
private Map<DimensionType, Integer> tickTracker = new HashMap<>();
@Override
public void onInitialize() {
ServerTickCallback.EVENT.register(server -> {
if (server.getTicks() % 200 == 0) { // Log every 200 ticks to verify the tick callback works on the server
LOGGER.info("Ticked Server at " + server.getTicks() + " ticks. (Legacy)");
}
});
ServerStartCallback.EVENT.register(server -> {
LOGGER.info("Started Server! (Legacy)");
});
ServerStopCallback.EVENT.register(server -> {
LOGGER.info("Stopping Server! (Legacy)");
});
WorldTickCallback.EVENT.register(world -> {
final int worldTicks = tickTracker.computeIfAbsent(world.dimension.getType(), k -> 0);
if (worldTicks % 200 == 0) { // Log every 200 ticks to verify the tick callback works on the server world
LOGGER.info("[LEGACY] Ticked World " + world.dimension.getType() + " - " + worldTicks + " ticks: " + world.getClass().getName());
}
this.tickTracker.put(world.dimension.getType(), worldTicks + 1);
});
}
}

View file

@ -0,0 +1,43 @@
/*
* 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.test.event.lifecycle.legacy.client;
import net.minecraft.text.LiteralText;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.event.client.ClientTickCallback;
import net.fabricmc.fabric.api.event.client.ItemTooltipCallback;
import net.fabricmc.fabric.test.event.lifecycle.legacy.LegacyLifecycleEventsTest;
public class LegacyClientLifecycleEventsTest implements ClientModInitializer {
private int ticks;
@Override
public void onInitializeClient() {
ClientTickCallback.EVENT.register(client -> {
this.ticks++; // Just track our own tick since the client doesn't have a ticks value.
if (this.ticks % 200 == 0) {
LegacyLifecycleEventsTest.LOGGER.info("Ticked Client at " + this.ticks + " ticks. (Legacy)");
}
});
ItemTooltipCallback.EVENT.register((stack, context, lines) -> {
lines.add(new LiteralText("A Legacy Tooltip"));
});
}
}

View file

@ -0,0 +1,19 @@
{
"schemaVersion": 1,
"id": "fabric-events-lifecycle-v0-testmod",
"name": "Fabric Events Lifecycle (v0) Test Mod",
"version": "1.0.0",
"environment": "*",
"license": "Apache-2.0",
"depends": {
"fabric-events-lifecycle-v0": "*"
},
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.event.lifecycle.legacy.LegacyLifecycleEventsTest"
],
"client": [
"net.fabricmc.fabric.test.event.lifecycle.legacy.client.LegacyClientLifecycleEventsTest"
]
}
}

View file

@ -0,0 +1,6 @@
archivesBaseName = "fabric-item-api-v1"
version = getSubprojectVersion(project, "1.0.0")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')
}

View file

@ -0,0 +1,48 @@
/*
* 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.client.item.v1;
import java.util.List;
import net.minecraft.client.item.TooltipContext;
import net.minecraft.item.ItemStack;
import net.minecraft.text.Text;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
@Environment(EnvType.CLIENT)
public interface ItemTooltipCallback {
/**
* Fired after the game has appended all base tooltip lines to the list.
*/
Event<ItemTooltipCallback> EVENT = EventFactory.createArrayBacked(ItemTooltipCallback.class, callbacks -> (stack, context, lines) -> {
for (ItemTooltipCallback callback : callbacks) {
callback.getTooltip(stack, context, lines);
}
});
/**
* Called when an item stack's tooltip is rendered. Text added to {@code lines} will be
* rendered with the tooltip.
*
* @param lines the list containing the lines of text displayed on the stack's tooltip
*/
void getTooltip(ItemStack stack, TooltipContext context, List<Text> lines);
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.event.lifecycle;
package net.fabricmc.fabric.mixin.item.client;
import java.util.List;
@ -28,10 +28,10 @@ import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.text.Text;
import net.fabricmc.fabric.api.event.client.ItemTooltipCallback;
import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback;
@Mixin(ItemStack.class)
public class MixinItemStack {
public abstract class ItemStackMixin {
@Inject(method = "getTooltip", at = @At("RETURN"))
private void getTooltip(PlayerEntity entity, TooltipContext tooltipContext, CallbackInfoReturnable<List<Text>> info) {
ItemTooltipCallback.EVENT.invoker().getTooltip((ItemStack) (Object) this, tooltipContext, info.getReturnValue());

Binary file not shown.

After

(image error) Size: 1.5 KiB

View file

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

View file

@ -0,0 +1,26 @@
{
"schemaVersion": 1,
"id": "fabric-item-api-v1",
"name": "Fabric Item API (v1)",
"version": "${version}",
"environment": "*",
"license": "Apache-2.0",
"icon": "assets/fabric-item-api-v1/icon.png",
"contact": {
"homepage": "https://fabricmc.net",
"irc": "irc://irc.esper.net:6667/fabric",
"issues": "https://github.com/FabricMC/fabric/issues",
"sources": "https://github.com/FabricMC/fabric"
},
"authors": [
"FabricMC"
],
"mixins": [
"fabric-item-api-v1.mixins.json"
],
"depends": {
"fabricloader": ">=0.4.0",
"fabric-api-base": "*"
},
"description": "Hooks for items"
}

View file

@ -0,0 +1,36 @@
/*
* 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.test.item.client;
import net.minecraft.text.LiteralText;
import net.minecraft.util.Formatting;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback;
@Environment(EnvType.CLIENT)
public class TooltipTests implements ClientModInitializer {
@Override
public void onInitializeClient() {
// Adds a tooltip to all items so testing can be verified easily.
ItemTooltipCallback.EVENT.register((stack, context, lines) -> {
lines.add(new LiteralText("Fancy Tooltips").formatted(Formatting.LIGHT_PURPLE));
});
}
}

View file

@ -0,0 +1,16 @@
{
"schemaVersion": 1,
"id": "fabric-item-api-v1-testmod",
"name": "Fabric Item API (v1) Test Mod",
"version": "1.0.0",
"environment": "*",
"license": "Apache-2.0",
"depends": {
"fabric-item-api-v1": "*"
},
"entrypoints": {
"client": [
"net.fabricmc.fabric.test.item.client.TooltipTests"
]
}
}

View file

@ -0,0 +1,6 @@
archivesBaseName = "fabric-lifecycle-events-v1"
version = getSubprojectVersion(project, "1.0.0")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')
}

View file

@ -0,0 +1,88 @@
/*
* 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.client.event.lifecycle.v1;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.profiler.Profiler;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
@Environment(EnvType.CLIENT)
public final class ClientBlockEntityEvents {
private ClientBlockEntityEvents() {
}
/**
* Called when a BlockEntity is loaded into a ClientWorld.
*
* <p>When this event is called, the block entity is already in the world.
*/
public static final Event<ClientBlockEntityEvents.Load> BLOCK_ENTITY_LOAD = EventFactory.createArrayBacked(ClientBlockEntityEvents.Load.class, callbacks -> (blockEntity, world) -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = world.getProfiler();
profiler.push("fabricClientBlockEntityLoad");
for (ClientBlockEntityEvents.Load callback : callbacks) {
profiler.push(EventFactory.getHandlerName(callback));
callback.onLoad(blockEntity, world);
profiler.pop();
}
profiler.pop();
} else {
for (ClientBlockEntityEvents.Load callback : callbacks) {
callback.onLoad(blockEntity, world);
}
}
});
/**
* Called when a BlockEntity is about to be unloaded from a ClientWorld.
*
* <p>When this event is called, the block entity is still present on the world.
*/
public static final Event<ClientBlockEntityEvents.Unload> BLOCK_ENTITY_UNLOAD = EventFactory.createArrayBacked(ClientBlockEntityEvents.Unload.class, callbacks -> (blockEntity, world) -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = world.getProfiler();
profiler.push("fabricClientBlockEntityUnload");
for (ClientBlockEntityEvents.Unload callback : callbacks) {
profiler.push(EventFactory.getHandlerName(callback));
callback.onUnload(blockEntity, world);
profiler.pop();
}
profiler.pop();
} else {
for (ClientBlockEntityEvents.Unload callback : callbacks) {
callback.onUnload(blockEntity, world);
}
}
});
public interface Load {
void onLoad(BlockEntity blockEntity, ClientWorld world);
}
public interface Unload {
void onUnload(BlockEntity blockEntity, ClientWorld world);
}
}

View file

@ -0,0 +1,88 @@
/*
* 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.client.event.lifecycle.v1;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.profiler.Profiler;
import net.minecraft.world.chunk.WorldChunk;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
@Environment(EnvType.CLIENT)
public final class ClientChunkEvents {
private ClientChunkEvents() {
}
/**
* Called when a chunk is loaded into a ClientWorld.
*
* <p>When this event is called, the chunk is already in the world.
*/
public static final Event<ClientChunkEvents.Load> CHUNK_LOAD = EventFactory.createArrayBacked(ClientChunkEvents.Load.class, callbacks -> (clientWorld, chunk) -> {
if (EventFactory.isProfilingEnabled()) {
Profiler profiler = clientWorld.getProfiler();
profiler.push("fabricClientChunkLoad");
for (ClientChunkEvents.Load callback : callbacks) {
profiler.push(EventFactory.getHandlerName(callback));
callback.onChunkLoad(clientWorld, chunk);
profiler.pop();
}
profiler.pop();
} else {
for (ClientChunkEvents.Load callback : callbacks) {
callback.onChunkLoad(clientWorld, chunk);
}
}
});
/**
* Called when a chunk is about to be unloaded from a ClientWorld.
*
* <p>When this event is called, the chunk is still present in the world.
*/
public static final Event<ClientChunkEvents.Unload> CHUNK_UNLOAD = EventFactory.createArrayBacked(ClientChunkEvents.Unload.class, callbacks -> (clientWorld, chunk) -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = clientWorld.getProfiler();
profiler.push("fabricClientChunkUnload");
for (ClientChunkEvents.Unload callback : callbacks) {
profiler.push(EventFactory.getHandlerName(callback));
callback.onChunkUnload(clientWorld, chunk);
profiler.pop();
}
profiler.pop();
} else {
for (ClientChunkEvents.Unload callback : callbacks) {
callback.onChunkUnload(clientWorld, chunk);
}
}
});
public interface Load {
void onChunkLoad(ClientWorld world, WorldChunk chunk);
}
public interface Unload {
void onChunkUnload(ClientWorld world, WorldChunk chunk);
}
}

View file

@ -0,0 +1,88 @@
/*
* 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.client.event.lifecycle.v1;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.util.profiler.Profiler;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
@Environment(EnvType.CLIENT)
public final class ClientEntityEvents {
public ClientEntityEvents() {
}
/**
* Called when an Entity is loaded into a ClientWorld.
*
* <p>When this event is called, the chunk is already in the world.
*/
public static final Event<ClientEntityEvents.Load> ENTITY_LOAD = EventFactory.createArrayBacked(ClientEntityEvents.Load.class, callbacks -> (entity, world) -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = world.getProfiler();
profiler.push("fabricClientEntityLoad");
for (ClientEntityEvents.Load callback : callbacks) {
profiler.push(EventFactory.getHandlerName(callback));
callback.onLoad(entity, world);
profiler.pop();
}
profiler.pop();
} else {
for (ClientEntityEvents.Load callback : callbacks) {
callback.onLoad(entity, world);
}
}
});
/**
* Called when an Entity is about to be unloaded from a ClientWorld.
*
* <p>When this event is called, the entity is still present in the world.
*/
public static final Event<ClientEntityEvents.Unload> ENTITY_UNLOAD = EventFactory.createArrayBacked(ClientEntityEvents.Unload.class, callbacks -> (entity, world) -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = world.getProfiler();
profiler.push("fabricClientEntityLoad");
for (ClientEntityEvents.Unload callback : callbacks) {
profiler.push(EventFactory.getHandlerName(callback));
callback.onUnload(entity, world);
profiler.pop();
}
profiler.pop();
} else {
for (ClientEntityEvents.Unload callback : callbacks) {
callback.onUnload(entity, world);
}
}
});
public interface Load {
void onLoad(Entity entity, ClientWorld world);
}
public interface Unload {
void onUnload(Entity entity, ClientWorld world);
}
}

View file

@ -0,0 +1,61 @@
/*
* 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.client.event.lifecycle.v1;
import net.minecraft.client.MinecraftClient;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
@Environment(EnvType.CLIENT)
public final class ClientLifecycleEvents {
private ClientLifecycleEvents() {
}
/**
* Called when Minecraft has started and it's client about to tick for the first time.
*
* <p>This occurs while the splash screen is displayed.
*/
public static final Event<ClientStarted> CLIENT_STARTED = EventFactory.createArrayBacked(ClientStarted.class, callbacks -> client -> {
for (ClientStarted callback : callbacks) {
callback.onClientStarted(client);
}
});
/**
* Called when Minecraft's client begins to stop.
* This is caused by quitting while in game, or closing the game window.
*
* <p>This will be called before the integrated server is stopped if it is running.
*/
public static final Event<ClientStopping> CLIENT_STOPPING = EventFactory.createArrayBacked(ClientStopping.class, callbacks -> client -> {
for (ClientStopping callback : callbacks) {
callback.onClientStopping(client);
}
});
public interface ClientStarted {
void onClientStarted(MinecraftClient client);
}
public interface ClientStopping {
void onClientStopping(MinecraftClient client);
}
}

View file

@ -0,0 +1,138 @@
/*
* 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.client.event.lifecycle.v1;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.profiler.Profiler;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
@Environment(EnvType.CLIENT)
public final class ClientTickEvents {
public ClientTickEvents() {
}
/**
* Called at the start of the client tick.
*/
public static final Event<StartTick> START_CLIENT_TICK = EventFactory.createArrayBacked(StartTick.class, callbacks -> client -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = client.getProfiler();
profiler.push("fabricStartClientTick");
for (StartTick event : callbacks) {
profiler.push(EventFactory.getHandlerName(event));
event.onStartTick(client);
profiler.pop();
}
profiler.pop();
} else {
for (StartTick event : callbacks) {
event.onStartTick(client);
}
}
});
/**
* Called at the end of the client tick.
*/
public static final Event<EndTick> END_CLIENT_TICK = EventFactory.createArrayBacked(EndTick.class, callbacks -> client -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = client.getProfiler();
profiler.push("fabricEndClientTick");
for (EndTick event : callbacks) {
profiler.push(EventFactory.getHandlerName(event));
event.onEndTick(client);
profiler.pop();
}
profiler.pop();
} else {
for (EndTick event : callbacks) {
event.onEndTick(client);
}
}
});
/**
* Called at the start of a ClientWorld's tick.
*/
public static final Event<StartWorldTick> START_WORLD_TICK = EventFactory.createArrayBacked(StartWorldTick.class, callbacks -> world -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = world.getProfiler();
profiler.push("fabricStartClientWorldTick");
for (StartWorldTick callback : callbacks) {
profiler.push(EventFactory.getHandlerName(callback));
callback.onStartTick(world);
profiler.pop();
}
profiler.pop();
} else {
for (StartWorldTick callback : callbacks) {
callback.onStartTick(world);
}
}
});
/**
* Called at the end of a ClientWorld's tick.
*
* <p>End of world tick may be used to start async computations for the next tick.
*/
public static final Event<EndWorldTick> END_WORLD_TICK = EventFactory.createArrayBacked(EndWorldTick.class, callbacks -> world -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = world.getProfiler();
profiler.push("fabricEndClientWorldTick");
for (EndWorldTick callback : callbacks) {
profiler.push(EventFactory.getHandlerName(callback));
callback.onEndTick(world);
profiler.pop();
}
profiler.pop();
} else {
for (EndWorldTick callback : callbacks) {
callback.onEndTick(world);
}
}
});
public interface StartTick {
void onStartTick(MinecraftClient client);
}
public interface EndTick {
void onEndTick(MinecraftClient client);
}
public interface StartWorldTick {
void onStartTick(ClientWorld world);
}
public interface EndWorldTick {
void onEndTick(ClientWorld world);
}
}

View file

@ -0,0 +1,85 @@
/*
* 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.block.entity.BlockEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.profiler.Profiler;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
public final class ServerBlockEntityEvents {
private ServerBlockEntityEvents() {
}
/**
* Called when an BlockEntity is loaded into a ServerWorld.
*
* <p>When this is event is called, the block entity is already in the world.
*/
public static final Event<ServerBlockEntityEvents.Load> BLOCK_ENTITY_LOAD = EventFactory.createArrayBacked(ServerBlockEntityEvents.Load.class, callbacks -> (blockEntity, world) -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = world.getProfiler();
profiler.push("fabricServerBlockEntityLoad");
for (ServerBlockEntityEvents.Load callback : callbacks) {
profiler.push(EventFactory.getHandlerName(callback));
callback.onLoad(blockEntity, world);
profiler.pop();
}
profiler.pop();
} else {
for (ServerBlockEntityEvents.Load callback : callbacks) {
callback.onLoad(blockEntity, world);
}
}
});
/**
* Called when an BlockEntity is about to be unloaded from a ServerWorld.
*
* <p>When this event is called, the block entity is still present on the world.
*/
public static final Event<Unload> BLOCK_ENTITY_UNLOAD = EventFactory.createArrayBacked(ServerBlockEntityEvents.Unload.class, callbacks -> (blockEntity, world) -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = world.getProfiler();
profiler.push("fabricServerBlockEntityUnload");
for (ServerBlockEntityEvents.Unload callback : callbacks) {
profiler.push(EventFactory.getHandlerName(callback));
callback.onUnload(blockEntity, world);
profiler.pop();
}
profiler.pop();
} else {
for (ServerBlockEntityEvents.Unload callback : callbacks) {
callback.onUnload(blockEntity, world);
}
}
});
public interface Load {
void onLoad(BlockEntity blockEntity, ServerWorld world);
}
public interface Unload {
void onUnload(BlockEntity blockEntity, ServerWorld world);
}
}

View file

@ -0,0 +1,85 @@
/*
* 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.server.world.ServerWorld;
import net.minecraft.util.profiler.Profiler;
import net.minecraft.world.chunk.WorldChunk;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
public final class ServerChunkEvents {
private ServerChunkEvents() {
}
/**
* Called when an chunk is loaded into a ServerWorld.
*
* <p>When this event is called, the chunk is already in the world.
*/
public static final Event<ServerChunkEvents.Load> CHUNK_LOAD = EventFactory.createArrayBacked(ServerChunkEvents.Load.class, callbacks -> (serverWorld, chunk) -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = serverWorld.getProfiler();
profiler.push("fabricServerChunkLoad");
for (ServerChunkEvents.Load callback : callbacks) {
profiler.push(EventFactory.getHandlerName(callback));
callback.onChunkLoad(serverWorld, chunk);
profiler.pop();
}
profiler.pop();
} else {
for (ServerChunkEvents.Load callback : callbacks) {
callback.onChunkLoad(serverWorld, chunk);
}
}
});
/**
* Called when an chunk is unloaded from a ServerWorld.
*
* <p>When this event is called, the chunk is still present in the world.
*/
public static final Event<ServerChunkEvents.Unload> CHUNK_UNLOAD = EventFactory.createArrayBacked(ServerChunkEvents.Unload.class, callbacks -> (serverWorld, chunk) -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = serverWorld.getProfiler();
profiler.push("fabricServerChunkUnload");
for (ServerChunkEvents.Unload callback : callbacks) {
profiler.push(EventFactory.getHandlerName(callback));
callback.onChunkUnload(serverWorld, chunk);
profiler.pop();
}
profiler.pop();
} else {
for (ServerChunkEvents.Unload callback : callbacks) {
callback.onChunkUnload(serverWorld, chunk);
}
}
});
public interface Load {
void onChunkLoad(ServerWorld world, WorldChunk chunk);
}
public interface Unload {
void onChunkUnload(ServerWorld world, WorldChunk chunk);
}
}

View file

@ -0,0 +1,59 @@
/*
* 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.entity.Entity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.profiler.Profiler;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
public final class ServerEntityEvents {
private ServerEntityEvents() {
}
/**
* Called when an Entity is loaded into a ServerWorld.
*
* <p>When this event is called, the entity is already in the world.
*
* <p>Note there is no corresponding unload event because entity unloads cannot be reliably tracked.
*/
public static final Event<ServerEntityEvents.Load> ENTITY_LOAD = EventFactory.createArrayBacked(ServerEntityEvents.Load.class, callbacks -> (entity, world) -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = world.getProfiler();
profiler.push("fabricServerEntityLoad");
for (ServerEntityEvents.Load callback : callbacks) {
profiler.push(EventFactory.getHandlerName(callback));
callback.onLoad(entity, world);
profiler.pop();
}
profiler.pop();
} else {
for (ServerEntityEvents.Load callback : callbacks) {
callback.onLoad(entity, world);
}
}
});
public interface Load {
void onLoad(Entity entity, ServerWorld world);
}
}

View file

@ -0,0 +1,77 @@
/*
* 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.server.MinecraftServer;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
public final class ServerLifecycleEvents {
private ServerLifecycleEvents() {
}
/**
* Called when a Minecraft server has started and is about to tick for the first time.
*
* <p>At this stage, all worlds are live.
*/
public static final Event<ServerStarted> SERVER_STARTED = EventFactory.createArrayBacked(ServerStarted.class, (callbacks) -> (server) -> {
for (ServerStarted callback : callbacks) {
callback.onServerStarted(server);
}
});
/**
* Called when a Minecraft server has started shutting down.
* This occurs before the server's network channel is closed and before any players are disconnected.
*
* <p>For example, an integrated server will begin stopping, but it's client may continue to run.
*
* <p>All worlds are still present and can be modified.
*/
public static final Event<ServerStopping> SERVER_STOPPING = EventFactory.createArrayBacked(ServerStopping.class, (callbacks) -> (server) -> {
for (ServerStopping callback : callbacks) {
callback.onServerStopping(server);
}
});
/**
* Called when a Minecraft server has stopped.
* All worlds have been closed and all (block)entities and players have been unloaded.
*
* <p>For example, an {@link net.fabricmc.api.EnvType#CLIENT integrated server} will begin stopping, but it's client may continue to run.
* Meanwhile for a {@link net.fabricmc.api.EnvType#SERVER dedicated server}, this will be the last event called.
*/
public static final Event<ServerStopped> SERVER_STOPPED = EventFactory.createArrayBacked(ServerStopped.class, callbacks -> server -> {
for (ServerStopped callback : callbacks) {
callback.onServerStopped(server);
}
});
public interface ServerStarted {
void onServerStarted(MinecraftServer server);
}
public interface ServerStopping {
void onServerStopping(MinecraftServer server);
}
public interface ServerStopped {
void onServerStopped(MinecraftServer server);
}
}

View file

@ -0,0 +1,135 @@
/*
* 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.server.MinecraftServer;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.profiler.Profiler;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
public final class ServerTickEvents {
private ServerTickEvents() {
}
/**
* Called at the start of the server tick.
*/
public static final Event<StartTick> START_SERVER_TICK = EventFactory.createArrayBacked(StartTick.class, callbacks -> server -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = server.getProfiler();
profiler.push("fabricStartServerTick");
for (StartTick event : callbacks) {
profiler.push(EventFactory.getHandlerName(event));
event.onStartTick(server);
profiler.pop();
}
profiler.pop();
} else {
for (StartTick event : callbacks) {
event.onStartTick(server);
}
}
});
/**
* Called at the end of the server tick.
*/
public static final Event<EndTick> END_SERVER_TICK = EventFactory.createArrayBacked(EndTick.class, callbacks -> server -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = server.getProfiler();
profiler.push("fabricEndServerTick");
for (EndTick event : callbacks) {
profiler.push(EventFactory.getHandlerName(event));
event.onEndTick(server);
profiler.pop();
}
profiler.pop();
} else {
for (EndTick event : callbacks) {
event.onEndTick(server);
}
}
});
/**
* Called at the start of a ServerWorld's tick.
*/
public static final Event<StartWorldTick> START_WORLD_TICK = EventFactory.createArrayBacked(StartWorldTick.class, callbacks -> world -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = world.getProfiler();
profiler.push("fabricStartServerWorldTick_" + world.dimension.getType().toString());
for (StartWorldTick callback : callbacks) {
profiler.push(EventFactory.getHandlerName(callback));
callback.onStartTick(world);
profiler.pop();
}
profiler.pop();
} else {
for (StartWorldTick callback : callbacks) {
callback.onStartTick(world);
}
}
});
/**
* Called at the end of a ServerWorld's tick.
*
* <p>End of world tick may be used to start async computations for the next tick.
*/
public static final Event<EndWorldTick> END_WORLD_TICK = EventFactory.createArrayBacked(EndWorldTick.class, callbacks -> world -> {
if (EventFactory.isProfilingEnabled()) {
final Profiler profiler = world.getProfiler();
profiler.push("fabricEndServerWorldTick_" + world.dimension.getType().toString());
for (EndWorldTick callback : callbacks) {
profiler.push(EventFactory.getHandlerName(callback));
callback.onEndTick(world);
profiler.pop();
}
profiler.pop();
} else {
for (EndWorldTick callback : callbacks) {
callback.onEndTick(world);
}
}
});
public interface StartTick {
void onStartTick(MinecraftServer server);
}
public interface EndTick {
void onEndTick(MinecraftServer server);
}
public interface StartWorldTick {
void onStartTick(ServerWorld world);
}
public interface EndWorldTick {
void onEndTick(ServerWorld world);
}
}

View file

@ -0,0 +1,76 @@
/*
* 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 java.util.Iterator;
import java.util.List;
import java.util.function.BooleanSupplier;
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 org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.Entity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.world.ServerWorld;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerBlockEntityEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
@Mixin(MinecraftServer.class)
public abstract class MinecraftServerMixin {
@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;setFavicon(Lnet/minecraft/server/ServerMetadata;)V", ordinal = 0), method = "run")
private void afterSetupServer(CallbackInfo info) {
ServerLifecycleEvents.SERVER_STARTED.invoker().onServerStarted((MinecraftServer) (Object) this);
}
@Inject(at = @At("HEAD"), method = "shutdown")
private void beforeShutdownServer(CallbackInfo info) {
ServerLifecycleEvents.SERVER_STOPPING.invoker().onServerStopping((MinecraftServer) (Object) this);
}
@Inject(at = @At("TAIL"), method = "shutdown")
private void afterShutdownServer(CallbackInfo info) {
ServerLifecycleEvents.SERVER_STOPPED.invoker().onServerStopped((MinecraftServer) (Object) this);
}
@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;tickWorlds(Ljava/util/function/BooleanSupplier;)V"), method = "tick")
private void onStartTick(BooleanSupplier shouldKeepTicking, CallbackInfo ci) {
ServerTickEvents.START_SERVER_TICK.invoker().onStartTick((MinecraftServer) (Object) this);
}
@Inject(at = @At("TAIL"), method = "tick")
private void onEndTick(BooleanSupplier shouldKeepTicking, CallbackInfo info) {
ServerTickEvents.END_SERVER_TICK.invoker().onEndTick((MinecraftServer) (Object) this);
}
/**
* When a world is closed, it means the world will be unloaded.
*/
@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerWorld;close()V"), method = "shutdown", locals = LocalCapture.CAPTURE_FAILEXCEPTION)
private void closeWorld(CallbackInfo ci, Iterator<ServerWorld> worlds, ServerWorld serverWorld) {
final List<Entity> entities = serverWorld.getEntities(null, entity -> true); // Get every single entity in the world
for (BlockEntity blockEntity : serverWorld.blockEntities) {
ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, serverWorld);
}
}
}

View file

@ -0,0 +1,52 @@
/*
* 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 java.util.function.BooleanSupplier;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
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.entity.Entity;
import net.minecraft.server.world.ServerWorld;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
@Mixin(ServerWorld.class)
public abstract class ServerWorldMixin {
@Shadow
private boolean ticking;
// Call our load event after vanilla has loaded the entity
@Inject(method = "loadEntityUnchecked", at = @At("TAIL"))
private void onLoadEntity(Entity entity, CallbackInfo ci) {
if (!this.ticking) { // Copy vanilla logic, we cannot load entities while the game is ticking entities
ServerEntityEvents.ENTITY_LOAD.invoker().onLoad(entity, (ServerWorld) (Object) this);
}
}
// Make sure "insideTick" is true before we call the start tick, so inject after it is set
@Inject(method = "tick", at = @At(value = "FIELD", target = "Lnet/minecraft/server/world/ServerWorld;insideTick:Z", opcode = Opcodes.PUTFIELD, ordinal = 0, shift = At.Shift.AFTER))
private void startWorldTick(BooleanSupplier shouldKeepTicking, CallbackInfo ci) {
ServerTickEvents.START_WORLD_TICK.invoker().onStartTick((ServerWorld) (Object) this);
}
}

View file

@ -0,0 +1,68 @@
/*
* 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 java.util.concurrent.CompletableFuture;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.server.world.ChunkHolder;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.server.world.ThreadedAnvilChunkStorage;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.WorldChunk;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents;
@Mixin(ThreadedAnvilChunkStorage.class)
public abstract class ThreadedAnvilChunkStorageMixin {
@Shadow
@Final
private ServerWorld world;
// Chunk (Un)Load events, An explanation:
// Must of this code is wrapped inside of futures and consumers, so it's generally a mess.
/**
* Injection is inside of tryUnloadChunk.
* We inject just after "setLoadedToWorld" is made false, since here the WorldChunk is guaranteed to be unloaded.
*/
@Inject(method = "method_18843", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/chunk/WorldChunk;setLoadedToWorld(Z)V", shift = At.Shift.AFTER))
private void onChunkUnload(ChunkHolder chunkHolder, CompletableFuture<Chunk> chunkFuture, long pos, Chunk chunk, CallbackInfo ci) {
ServerChunkEvents.CHUNK_UNLOAD.invoker().onChunkUnload(this.world, (WorldChunk) chunk);
}
/**
* Injection is inside of convertToFullChunk?
*
* <p>The following is expected contractually
*
* <ul><li>the chunk being loaded MUST be a WorldChunk.
* <li>everything within the chunk has been loaded into the world. Entities, BlockEntities, etc.</ul>
*/
@Inject(method = "method_17227", at = @At("TAIL"))
private void onChunkLoad(ChunkHolder chunkHolder, Chunk protoChunk, CallbackInfoReturnable<Chunk> callbackInfoReturnable) {
// We fire the event at TAIL since the chunk is guaranteed to be a WorldChunk then.
ServerChunkEvents.CHUNK_LOAD.invoker().onChunkLoad(this.world, (WorldChunk) callbackInfoReturnable.getReturnValue());
}
}

View file

@ -0,0 +1,90 @@
/*
* 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 java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.Slice;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.profiler.Profiler;
import net.minecraft.world.World;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerBlockEntityEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
@Mixin(World.class)
public abstract class WorldMixin {
@Shadow
public abstract boolean isClient();
@Shadow
public abstract Profiler getProfiler();
@Inject(method = "addBlockEntity", at = @At("TAIL"))
protected void onLoadBlockEntity(BlockEntity blockEntity, CallbackInfoReturnable<Boolean> cir) {
if (!this.isClient()) { // Only fire this event if we are a server world
ServerBlockEntityEvents.BLOCK_ENTITY_LOAD.invoker().onLoad(blockEntity, (ServerWorld) (Object) this);
}
}
// Mojang what hell, why do you need three ways to unload block entities
@Inject(method = "removeBlockEntity", at = @At(value = "INVOKE", target = "Ljava/util/List;remove(Ljava/lang/Object;)Z", ordinal = 1), locals = LocalCapture.CAPTURE_FAILEXCEPTION)
protected void onUnloadBlockEntity(BlockPos pos, CallbackInfo ci, BlockEntity blockEntity) {
if (!this.isClient()) { // Only fire this event if we are a server world
ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, (ServerWorld) (Object) this);
}
}
@Inject(method = "tickBlockEntities", at = @At(value = "INVOKE", target = "Ljava/util/List;remove(Ljava/lang/Object;)Z"), slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiler/Profiler;pop()V"), to = @At(value = "INVOKE", target = "Lnet/minecraft/world/chunk/WorldChunk;removeBlockEntity(Lnet/minecraft/util/math/BlockPos;)V")), locals = LocalCapture.CAPTURE_FAILEXCEPTION)
protected void onRemoveBlockEntity(CallbackInfo ci, Profiler profiler, Iterator iterator, BlockEntity blockEntity) {
if (!this.isClient()) {
ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, (ServerWorld) (Object) this);
}
}
@Redirect(method = "tickBlockEntities", at = @At(value = "INVOKE", target = "Ljava/util/List;removeAll(Ljava/util/Collection;)Z", ordinal = 1))
protected boolean onPurgeRemovedBlockEntities(List<BlockEntity> blockEntityList, Collection<BlockEntity> removals) {
if (!this.isClient()) {
for (BlockEntity removal : removals) {
ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(removal, (ServerWorld) (Object) this);
}
}
// Mimic vanilla logic
return blockEntityList.removeAll(removals);
}
@Inject(at = @At("RETURN"), method = "tickBlockEntities")
protected void tickWorldAfterBlockEntities(CallbackInfo ci) {
if (!this.isClient()) {
ServerTickEvents.END_WORLD_TICK.invoker().onEndTick((ServerWorld) (Object) this);
}
}
}

View file

@ -0,0 +1,55 @@
/*
* 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.client;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.client.world.ClientChunkManager;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.PacketByteBuf;
import net.minecraft.world.biome.source.BiomeArray;
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.ClientChunkEvents;
@Environment(EnvType.CLIENT)
@Mixin(ClientChunkManager.class)
public abstract class ClientChunkManagerMixin {
@Final
@Shadow
private ClientWorld world;
@Inject(method = "loadChunkFromPacket", at = @At("TAIL")) // 1.16 has a boolean param here. I think it means whether the packet is complete.
private void onChunkLoad(int chunkX, int chunkZ, BiomeArray biomes, PacketByteBuf buf, CompoundTag tag, int k, /* boolean complete, */ CallbackInfoReturnable<WorldChunk> cir) {
ClientChunkEvents.CHUNK_LOAD.invoker().onChunkLoad(this.world, cir.getReturnValue());
}
@Inject(method = "unload", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/world/ClientChunkManager$ClientChunkMap;compareAndSet(ILnet/minecraft/world/chunk/WorldChunk;Lnet/minecraft/world/chunk/WorldChunk;)Lnet/minecraft/world/chunk/WorldChunk;", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILEXCEPTION)
private void onChunkUnload(int chunkX, int chunkZ, CallbackInfo ci, int i, WorldChunk chunk) {
ClientChunkEvents.CHUNK_UNLOAD.invoker().onChunkUnload(this.world, chunk);
}
}

View file

@ -0,0 +1,94 @@
/*
* 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.client;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
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.block.entity.BlockEntity;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.packet.GameJoinS2CPacket;
import net.minecraft.client.network.packet.PlayerRespawnS2CPacket;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
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;
@Environment(EnvType.CLIENT)
@Mixin(ClientPlayNetworkHandler.class)
public abstract class ClientPlayNetworkHandlerMixin {
@Shadow
private ClientWorld world;
@Inject(method = "onPlayerRespawn", at = @At(value = "NEW", target = "net/minecraft/client/world/ClientWorld"))
private void onPlayerRespawn(PlayerRespawnS2CPacket packet, CallbackInfo ci) {
// If a world already exists, we need to unload all (block)entities in the world.
if (this.world != null) {
for (Entity entity : world.getEntities()) {
ClientEntityEvents.ENTITY_UNLOAD.invoker().onUnload(entity, this.world);
}
for (BlockEntity blockEntity : world.blockEntities) {
ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, this.world);
// No need to clear the `tickingBlockEntities` list since it will be null in just an instant
}
}
}
/**
* An explanation why we unload entities during onGameJoin:
* Proxies such as Waterfall may send another Game Join packet if entity meta rewrite is disabled, so we will cover ourselves.
* Velocity by default will send a Game Join packet when the player changes servers, which will create a new client world.
* Also anyone can send another GameJoinPacket at any time, so we need to watch out.
*/
@Inject(method = "onGameJoin", at = @At(value = "NEW", target = "net/minecraft/client/world/ClientWorld"))
private void onGameJoin(GameJoinS2CPacket packet, CallbackInfo ci) {
// If a world already exists, we need to unload all (block)entities in the world.
if (this.world != null) {
for (Entity entity : world.getEntities()) {
ClientEntityEvents.ENTITY_UNLOAD.invoker().onUnload(entity, this.world);
}
for (BlockEntity blockEntity : world.blockEntities) {
ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, this.world);
// No need to clear the `tickingBlockEntities` list since it will be null in just an instant
}
}
}
// Called when the client disconnects from a server.
@Inject(method = "clearWorld", at = @At("HEAD"))
private void onClearWorld(CallbackInfo ci) {
// If a world already exists, we need to unload all (block)entities in the world.
if (this.world != null) {
for (Entity entity : world.getEntities()) {
ClientEntityEvents.ENTITY_UNLOAD.invoker().onUnload(entity, this.world);
}
for (BlockEntity blockEntity : world.blockEntities) {
ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, this.world);
// No need to clear the `tickingBlockEntities` list since it will be null in just an instant
}
}
}
}

View file

@ -0,0 +1,93 @@
/*
* 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.client;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.profiler.Profiler;
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.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.mixin.event.lifecycle.WorldMixin;
@Environment(EnvType.CLIENT)
@Mixin(ClientWorld.class)
public abstract class ClientWorldMixin extends WorldMixin {
// Call our load event after vanilla has loaded the entity
@Inject(method = "addEntityPrivate", at = @At("TAIL"))
private void onEntityLoad(int id, Entity entity, CallbackInfo ci) {
ClientEntityEvents.ENTITY_LOAD.invoker().onLoad(entity, (ClientWorld) (Object) this);
}
// Call our unload event before vanilla does.
@Inject(method = "finishRemovingEntity", at = @At("HEAD"))
private void onEntityUnload(Entity entity, CallbackInfo ci) {
ClientEntityEvents.ENTITY_UNLOAD.invoker().onUnload(entity, (ClientWorld) (Object) this);
}
// We override our injection on the clientworld so only the client's block entity invocations will run
@Override
protected void onLoadBlockEntity(BlockEntity blockEntity, CallbackInfoReturnable<Boolean> cir) {
ClientBlockEntityEvents.BLOCK_ENTITY_LOAD.invoker().onLoad(blockEntity, (ClientWorld) (Object) this);
}
// We override our injection on the clientworld so only the client's block entity invocations will run
@Override
protected void onUnloadBlockEntity(BlockPos pos, CallbackInfo ci, BlockEntity blockEntity) {
ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, (ClientWorld) (Object) this);
}
@Override
protected void onRemoveBlockEntity(CallbackInfo ci, Profiler profiler, Iterator iterator, BlockEntity blockEntity) {
ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, (ClientWorld) (Object) this);
}
@Override
protected boolean onPurgeRemovedBlockEntities(List<BlockEntity> blockEntityList, Collection<BlockEntity> removals) {
for (BlockEntity removal : removals) {
ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(removal, (ClientWorld) (Object) this);
}
return super.onPurgeRemovedBlockEntities(blockEntityList, removals); // Call super
}
// We override our injection on the clientworld so only the client world's tick invocations will run
@Override
protected void tickWorldAfterBlockEntities(CallbackInfo ci) {
ClientTickEvents.END_WORLD_TICK.invoker().onEndTick((ClientWorld) (Object) this);
}
@Inject(method = "tickEntities", at = @At("HEAD"))
private void startWorldTick(CallbackInfo ci) {
ClientTickEvents.START_WORLD_TICK.invoker().onStartTick((ClientWorld) (Object) this);
}
}

View file

@ -0,0 +1,54 @@
/*
* 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.client;
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.MinecraftClient;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
@Environment(EnvType.CLIENT)
@Mixin(MinecraftClient.class)
public abstract class MinecraftClientMixin {
@Inject(at = @At("HEAD"), method = "tick")
private void onStartTick(CallbackInfo info) {
ClientTickEvents.START_CLIENT_TICK.invoker().onStartTick((MinecraftClient) (Object) this);
}
@Inject(at = @At("RETURN"), method = "tick")
private void onEndTick(CallbackInfo info) {
ClientTickEvents.END_CLIENT_TICK.invoker().onEndTick((MinecraftClient) (Object) this);
}
@Inject(at = @At(value = "INVOKE", target = "Lorg/apache/logging/log4j/Logger;info(Ljava/lang/String;)V", shift = At.Shift.AFTER), method = "stop")
private void onStopping(CallbackInfo ci) {
ClientLifecycleEvents.CLIENT_STOPPING.invoker().onClientStopping((MinecraftClient) (Object) this);
}
// We inject after the thread field is set so `ThreadExecutor#getThread` will work
@Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/client/MinecraftClient;thread:Ljava/lang/Thread;", shift = At.Shift.AFTER), method = "run")
private void onStart(CallbackInfo ci) {
ClientLifecycleEvents.CLIENT_STARTED.invoker().onClientStarted((MinecraftClient) (Object) this);
}
}

Binary file not shown.

After

(image error) Size: 1.5 KiB

View file

@ -0,0 +1,20 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.event.lifecycle",
"compatibilityLevel": "JAVA_8",
"mixins": [
"MinecraftServerMixin",
"ServerWorldMixin",
"ThreadedAnvilChunkStorageMixin",
"WorldMixin"
],
"client": [
"client.ClientChunkManagerMixin",
"client.ClientPlayNetworkHandlerMixin",
"client.ClientWorldMixin",
"client.MinecraftClientMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,26 @@
{
"schemaVersion": 1,
"id": "fabric-lifecycle-events-v1",
"name": "Fabric Lifecycle Events (v1)",
"version": "${version}",
"environment": "*",
"license": "Apache-2.0",
"icon": "assets/fabric-lifecycle-events-v1/icon.png",
"contact": {
"homepage": "https://fabricmc.net",
"irc": "irc://irc.esper.net:6667/fabric",
"issues": "https://github.com/FabricMC/fabric/issues",
"sources": "https://github.com/FabricMC/fabric"
},
"authors": [
"FabricMC"
],
"mixins": [
"fabric-lifecycle-events-v1.mixins.json"
],
"depends": {
"fabricloader": ">=0.4.0",
"fabric-api-base": "*"
},
"description": "Events for the game's lifecycle."
}

View file

@ -0,0 +1,77 @@
/*
* 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.test.event.lifecycle;
import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.Logger;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.registry.Registry;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerBlockEntityEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
public class ServerBlockEntityLifecycleTests implements ModInitializer {
private List<BlockEntity> serverBlockEntities = new ArrayList<>();
@Override
public void onInitialize() {
final Logger logger = ServerLifecycleTests.LOGGER;
ServerBlockEntityEvents.BLOCK_ENTITY_LOAD.register((blockEntity, world) -> {
this.serverBlockEntities.add(blockEntity);
logger.info("[SERVER] LOADED " + Registry.BLOCK_ENTITY_TYPE.getId(blockEntity.getType()).toString() + " - BlockEntities: " + this.serverBlockEntities.size());
});
ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.register((blockEntity, world) -> {
this.serverBlockEntities.remove(blockEntity);
logger.info("[SERVER] UNLOADED " + Registry.BLOCK_ENTITY_TYPE.getId(blockEntity.getType()).toString() + " - BlockEntities: " + this.serverBlockEntities.size());
});
ServerTickEvents.END_SERVER_TICK.register(minecraftServer -> {
if (minecraftServer.getTicks() % 200 == 0) {
int entities = 0;
logger.info("[SERVER] Tracked BlockEntities:" + this.serverBlockEntities.size() + " Ticked at: " + minecraftServer.getTicks() + "ticks");
for (ServerWorld world : minecraftServer.getWorlds()) {
int worldEntities = world.blockEntities.size();
logger.info("[SERVER] Tracked BlockEntities in " + world.dimension.getType().toString() + " - " + worldEntities);
entities += worldEntities;
}
logger.info("[SERVER] Actual Total BlockEntities: " + entities);
if (entities != this.serverBlockEntities.size()) {
logger.error("[SERVER] Mismatch in tracked blockentities and actual blockentities");
}
}
});
ServerLifecycleEvents.SERVER_STOPPED.register(minecraftServer -> {
logger.info("[SERVER] Disconnected. Tracking: " + this.serverBlockEntities.size() + " blockentities");
if (this.serverBlockEntities.size() != 0) {
logger.error("[SERVER] Mismatch in tracked blockentities, expected 0");
}
});
}
}

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.test.event.lifecycle;
import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.Logger;
import net.minecraft.entity.Entity;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents;
/**
* Tests related to the lifecycle of entities.
*/
public class ServerEntityLifecycleTests implements ModInitializer {
private List<Entity> serverEntities = new ArrayList<>();
@Override
public void onInitialize() {
final Logger logger = ServerLifecycleTests.LOGGER;
ServerEntityEvents.ENTITY_LOAD.register((entity, world) -> {
this.serverEntities.add(entity);
logger.info("[SERVER] LOADED " + entity.toString() + " - Entities: " + this.serverEntities.size());
});
}
}

View file

@ -0,0 +1,45 @@
/*
* 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.test.event.lifecycle;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
/**
* Tests related to the lifecycle of a server.
*/
public class ServerLifecycleTests implements ModInitializer {
public static final Logger LOGGER = LogManager.getLogger("LifecycleEventsTest");
@Override
public void onInitialize() {
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
LOGGER.info("Started Server!");
});
ServerLifecycleEvents.SERVER_STOPPING.register(server -> {
LOGGER.info("Stopping Server!");
});
ServerLifecycleEvents.SERVER_STOPPED.register(server -> {
LOGGER.info("Stopped Server!");
});
}
}

View file

@ -0,0 +1,58 @@
/*
* 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.test.event.lifecycle;
import java.util.HashMap;
import java.util.Map;
import net.minecraft.world.dimension.DimensionType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
/**
* Test related to ticking events on the server.
*/
public class ServerTickTests implements ModInitializer {
private Map<DimensionType, Integer> tickTracker = new HashMap<>();
@Override
public void onInitialize() {
ServerTickEvents.END_SERVER_TICK.register(server -> {
if (server.getTicks() % 200 == 0) { // Log every 200 ticks to verify the tick callback works on the server
ServerLifecycleTests.LOGGER.info("Ticked Server at " + server.getTicks() + " ticks.");
}
});
ServerTickEvents.START_WORLD_TICK.register(world -> {
// Verify we are inside the tick
if (!world.isInsideTick()) {
throw new AssertionError("Start tick event should be fired while ServerWorld is inside of tick");
}
});
ServerTickEvents.END_WORLD_TICK.register(world -> {
final int worldTicks = tickTracker.computeIfAbsent(world.dimension.getType(), k -> 0);
if (worldTicks % 200 == 0) { // Log every 200 ticks to verify the tick callback works on the server world
ServerLifecycleTests.LOGGER.info("Ticked Server World - " + worldTicks + " ticks:" + world.dimension.getType());
}
this.tickTracker.put(world.dimension.getType(), worldTicks + 1);
});
}
}

View file

@ -0,0 +1,73 @@
/*
* 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.test.event.lifecycle.client;
import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.Logger;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.util.registry.Registry;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientBlockEntityEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.test.event.lifecycle.ServerLifecycleTests;
public class ClientBlockEntityLifecycleTests implements ClientModInitializer {
private List<BlockEntity> clientBlockEntities = new ArrayList<>();
private int clientTicks;
@Override
public void onInitializeClient() {
final Logger logger = ServerLifecycleTests.LOGGER;
ClientBlockEntityEvents.BLOCK_ENTITY_LOAD.register((blockEntity, world) -> {
this.clientBlockEntities.add(blockEntity);
logger.info("[CLIENT]" + " LOADED " + Registry.BLOCK_ENTITY_TYPE.getId(blockEntity.getType()).toString() + " - BlockEntities: " + this.clientBlockEntities.size());
});
ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.register((blockEntity, world) -> {
this.clientBlockEntities.remove(blockEntity);
logger.info("[CLIENT]" + " UNLOADED " + Registry.BLOCK_ENTITY_TYPE.getId(blockEntity.getType()).toString() + " - BlockEntities: " + this.clientBlockEntities.size());
});
ClientTickEvents.END_CLIENT_TICK.register(client -> {
if (this.clientTicks++ % 200 == 0 && client.world != null) {
final int blockEntities = client.world.blockEntities.size();
logger.info("[CLIENT] Tracked BlockEntities:" + this.clientBlockEntities.size() + " Ticked at: " + this.clientTicks + "ticks");
logger.info("[CLIENT] Actual BlockEntities: " + client.world.blockEntities.size());
if (blockEntities != this.clientBlockEntities.size()) {
logger.error("[CLIENT] Mismatch in tracked blockentities and actual blockentities");
}
}
});
ServerLifecycleEvents.SERVER_STOPPED.register(minecraftServer -> {
if (!minecraftServer.isDedicated()) { // fixme: Use ClientNetworking#PLAY_DISCONNECTED instead of the server stop callback for testing.
logger.info("[CLIENT] Disconnected. Tracking: " + this.clientBlockEntities.size() + " blockentities");
if (this.clientBlockEntities.size() != 0) {
logger.error("[CLIENT] Mismatch in tracked blockentities, expected 0");
}
}
});
}
}

View file

@ -0,0 +1,80 @@
/*
* 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.test.event.lifecycle.client;
import java.util.ArrayList;
import java.util.List;
import com.google.common.collect.Iterables;
import org.apache.logging.log4j.Logger;
import net.minecraft.entity.Entity;
import net.minecraft.util.registry.Registry;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.test.event.lifecycle.ServerLifecycleTests;
/**
* Tests related to the lifecycle of entities.
*/
@Environment(EnvType.CLIENT)
public class ClientEntityLifecycleTests implements ClientModInitializer {
private List<Entity> clientEntities = new ArrayList<>();
private int clientTicks;
@Override
public void onInitializeClient() {
final Logger logger = ServerLifecycleTests.LOGGER;
ClientEntityEvents.ENTITY_LOAD.register((entity, world) -> {
this.clientEntities.add(entity);
logger.info("[CLIENT]" + " LOADED " + Registry.ENTITY_TYPE.getId(entity.getType()).toString() + " - Entities: " + this.clientEntities.size());
});
ClientEntityEvents.ENTITY_UNLOAD.register((entity, world) -> {
this.clientEntities.remove(entity);
logger.info("[CLIENT]" + " UNLOADED " + Registry.ENTITY_TYPE.getId(entity.getType()).toString() + " - Entities: " + this.clientEntities.size());
});
ClientTickEvents.END_CLIENT_TICK.register(client -> {
if (this.clientTicks++ % 200 == 0 && client.world != null) {
final int entities = Iterables.toArray(client.world.getEntities(), Entity.class).length;
logger.info("[CLIENT] Tracked Entities:" + this.clientEntities.size() + " Ticked at: " + this.clientTicks + "ticks");
logger.info("[CLIENT] Actual Entities: " + entities);
if (entities != this.clientEntities.size()) {
logger.error("[CLIENT] Mismatch in tracked entities and actual entities");
}
}
});
ServerLifecycleEvents.SERVER_STOPPED.register(minecraftServer -> {
if (!minecraftServer.isDedicated()) { // fixme: Use ClientNetworking#PLAY_DISCONNECTED instead of the server stop callback for testing.
logger.info("[CLIENT] Disconnected. Tracking: " + this.clientEntities.size() + " entities");
if (this.clientEntities.size() != 0) {
logger.error("[CLIENT] Mismatch in tracked entities, expected 0");
}
}
});
}
}

View file

@ -0,0 +1,38 @@
/*
* 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.test.event.lifecycle.client;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
@Environment(EnvType.CLIENT)
public class ClientLifecycleTests implements ClientModInitializer {
@Override
public void onInitializeClient() {
ClientLifecycleEvents.CLIENT_STARTED.register(client -> {
client.submitAndJoin(() -> { // This should fail if the client thread was not bound yet.
System.out.println("Started the client");
});
});
ClientLifecycleEvents.CLIENT_STOPPING.register(client -> {
System.out.println("Client has started stopping!");
});
}
}

View file

@ -0,0 +1,55 @@
/*
* 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.test.event.lifecycle.client;
import java.util.HashMap;
import java.util.Map;
import net.minecraft.world.dimension.DimensionType;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.test.event.lifecycle.ServerLifecycleTests;
@Environment(EnvType.CLIENT)
public class ClientTickTests implements ClientModInitializer {
private Map<DimensionType, Integer> tickTracker = new HashMap<>();
private int ticks;
@Override
public void onInitializeClient() {
ClientTickEvents.END_CLIENT_TICK.register(client -> {
this.ticks++; // Just track our own tick since the client doesn't have a ticks value.
if (this.ticks % 200 == 0) {
ServerLifecycleTests.LOGGER.info("Ticked Client at " + this.ticks + " ticks.");
}
});
ClientTickEvents.END_WORLD_TICK.register(world -> {
final int worldTicks = this.tickTracker.computeIfAbsent(world.dimension.getType(), k -> 0);
if (worldTicks % 200 == 0) { // Log every 200 ticks to verify the tick callback works on the client world
ServerLifecycleTests.LOGGER.info("Ticked Client World - " + worldTicks + " ticks:" + world.dimension.getType());
}
this.tickTracker.put(world.dimension.getType(), worldTicks + 1);
});
}
}

View file

@ -0,0 +1,25 @@
{
"schemaVersion": 1,
"id": "fabric-lifecycle-events-v1-testmod",
"name": "Fabric Lifecycle Events (v1) Test Mod",
"version": "1.0.0",
"environment": "*",
"license": "Apache-2.0",
"depends": {
"fabric-lifecycle-events-v1": "*"
},
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.event.lifecycle.ServerBlockEntityLifecycleTests",
"net.fabricmc.fabric.test.event.lifecycle.ServerEntityLifecycleTests",
"net.fabricmc.fabric.test.event.lifecycle.ServerLifecycleTests",
"net.fabricmc.fabric.test.event.lifecycle.ServerTickTests"
],
"client": [
"net.fabricmc.fabric.test.event.lifecycle.client.ClientBlockEntityLifecycleTests",
"net.fabricmc.fabric.test.event.lifecycle.client.ClientEntityLifecycleTests",
"net.fabricmc.fabric.test.event.lifecycle.client.ClientLifecycleTests",
"net.fabricmc.fabric.test.event.lifecycle.client.ClientTickTests",
]
}
}

View file

@ -24,9 +24,11 @@ include 'fabric-content-registries-v0'
include 'fabric-crash-report-info-v1'
include 'fabric-events-interaction-v0'
include 'fabric-events-lifecycle-v0'
include 'fabric-item-api-v1'
include 'fabric-item-groups-v0'
include 'fabric-keybindings-v0'
include 'fabric-key-binding-api-v1'
include 'fabric-lifecycle-events-v1'
include 'fabric-loot-tables-v1'
include 'fabric-mining-levels-v0'
include 'fabric-models-v0'