Jump to content

[SOLVED][1.7.2] Clarification on details of event cancellation


Recommended Posts

Posted

Sorry if this has been asked before, but quick search didn't find it.  And too lazy to experiment ...

 

Question 1.  If one mod cancels an event, it is cancelled for other mods as well, right?  I assume that the mod that gets priority is the one that registers the handler first, which (since most mods register them during the FMLInitializationEvent) is normally based on the order that mods are loaded, right?

 

Question 2.  If there is a series of event subclasses, like Pre and Post, or Start and Finish, does cancelling the earlier event also cancel the later event?  I'm assuming so as it would presumably break the vanilla code if the expected earlier code doesn't run, but this also seems problematic -- if I handle a Pre event and cancel the vanilla handling then I can't do any Post event (or I guess I would have to fire that myself?).

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Posted

Question 1: I believe that when you use the @ForgeSubscribe annotation (if that is still in use), there is a parameter that you can add that is an enum involving priority. The higher the priority set on your event, the quicker it will be called when the event occurs. So if your mod has a higher priority than another mod, and you cancel the event, it *should* cancel it for the other mod as well seeing it is further down the chain.

 

Question 2: Good question... I am not too sure on that one.

We all stuff up sometimes... But I seem to be at the bottom of that pot.

Posted

Question 1: I believe that when you use the @ForgeSubscribe annotation (if that is still in use), there is a parameter that you can add that is an enum involving priority. The higher the priority set on your event, the quicker it will be called when the event occurs. So if your mod has a higher priority than another mod, and you cancel the event, it *should* cancel it for the other mod as well seeing it is further down the chain.

 

Perfect.  In 1.7.2 the annotation has changed to @Subscribe but I checked it out more closely and there is indeed ability to put priority, where EventPriority enum s defined as possible:

public enum EventPriority
{
    /*Priority of event listeners, listeners will be sorted with respect to this priority level.
     * 
     * Note:
     *   Due to using a ArrayList in the ListenerList,
     *   these need to stay in a contiguous index starting at 0. {Default ordinal} 
     */
    HIGHEST, //First to execute
    HIGH,
    NORMAL,
    LOW,
    LOWEST //Last to execute
}

 

Of course this is still a bit silly because every mod author can make their event handlers highest priority, and in that case I think it would still end up the way I mentioned -- i.e. handled in order that mods register their handlers.  So the result is that there is no guarantee that another mod won't intercept your handling.  But I guess that is standard kind of issue related to mod compatibility...

 

Still interested if people know the answer to #2.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Posted

Jabelar,

The Pre/Post thing depends.  The surefire way to know on the event in question is to search through the minecraft code for the hooks. 

 

I looked through this for player render.  The Pre will prevent the player from rendering.  The Post will tell you the render event is over and you can do your own.

 

For example under the doRender method for RenderPlayer

 

Early in the method the following is inserted

if (MinecraftForge.EVENT_BUS.post(new RenderPlayerEvent.Pre(par1AbstractClientPlayer, this, par9))) return;

 

Later in the method the following is inserted

MinecraftForge.EVENT_BUS.post(new RenderPlayerEvent.Post(par1AbstractClientPlayer, this, par9));

 

In this case, if you cancel the Pre, the Post will never get called.

 

I've seen in other cases this isn't necessarily true based upon how they set up the hooks.  They might have just set a variable false instead of exiting the method.

 

 

 

Long time Bukkit & Forge Programmer

Happy to try and help

Posted

Jabelar,

The Pre/Post thing depends.  The surefire way to know on the event in question is to search through the minecraft code for the hooks. 

 

I looked through this for player render.  The Pre will prevent the player from rendering.  The Post will tell you the render event is over and you can do your own.

 

For example under the doRender method for RenderPlayer

 

Early in the method the following is inserted

if (MinecraftForge.EVENT_BUS.post(new RenderPlayerEvent.Pre(par1AbstractClientPlayer, this, par9))) return;

 

Later in the method the following is inserted

MinecraftForge.EVENT_BUS.post(new RenderPlayerEvent.Post(par1AbstractClientPlayer, this, par9));

 

In this case, if you cancel the Pre, the Post will never get called.

 

I've seen in other cases this isn't necessarily true based upon how they set up the hooks.  They might have just set a variable false instead of exiting the method.

 

Yeah, I was kinda expecting this was an "it depends" question.  Since the posting of events is just code scattered throughout the source I could see where it might be implemented inconsistently.

 

Of course the other point is that you can (like Sequituri mentions) subscribe to canceled events anyway (probably recommended for all your subscriptions if you're worried about other mods canceling events).

 

So I think the recommended thing to do is to subscribe at HIGHEST and receiveCanceled=true and if you run across known mod incompatibilities you could check isCanceled() and if that is true you can see what mods are loaded and try to figure out how to modify your handling if necessary.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Posted

So I think the recommended thing to do is to subscribe at HIGHEST and receiveCanceled=true and if you run across known mod incompatibilities you could check isCanceled() and if that is true you can see what mods are loaded and try to figure out how to modify your handling if necessary.

I wouldn't recommend that as a blanket statement, it really depends on what your event handler is supposed to do. Most of my events are all normal priority and don't receive canceled; some are set to LOWEST priority so they are called last in the line, because I want to make sure every other mod gets to modify that event first, for example, and perhaps don't even want to handle it if another mod cancels it; I only use HIGHEST if the event should be handled first, but if everyone set theirs to HIGHEST, it would break this unspoken 'contract', and thus potentially break / interfere with various mods.

 

For the majority of events, you will want NORMAL priority and don't need to receive canceled, in my opinion, unless you have specific reasons for doing otherwise.

Posted

Most of my events are all normal priority and don't receive canceled; some are set to LOWEST priority so they are called last in the line, because I want to make sure every other mod gets to modify that event first, for example, and perhaps don't even want to handle it if another mod cancels it; I only use HIGHEST if the event should be handled first, but if everyone set theirs to HIGHEST, it would break this unspoken 'contract', and thus potentially break / interfere with various mods.

 

For the majority of events, you will want NORMAL priority and don't need to receive canceled, in my opinion, unless you have specific reasons for doing otherwise.

 

I can't think of many situations where a modder can tolerate to have an event that they are handling cancelled -- I mean you're handling the event for a reason and missing it will presumably break some feature of your mod.  I expect most mods that cancel the event are doing so to prevent the vanilla behavior, not other mods.  I really think most people should want to receive cancelled events -- it can't really hurt and can definitely help.

 

Regarding priority, I think it depends on whether your handler does any cancellation itself.  If you don't cancel the event, then I think it is reasonable to set to HIGHEST because you'll still be passing the event to others and you minimize the risk of being cancelled yourself.  However, I suppose as long as you receive cancelled events then I guess it doesn't matter much what your priority is.

 

In the end though, full mod compatibility is a case by case thing that probably needs some discussion between mod authors or at least needs some understanding of the specific case.  It would also be interesting to come up with a scheme that mitigates compatibility issues by setting receiveCanceled=true, checking isCanceled() and then checking what other mods are loaded (sadly the listener bus seems to be private so can't be analyzed directly) to guess which one is the "culprit" causing the cancelation.

 

So I think I'm going to go with the philosophy of always receiveCanceled but priority=EventPriority.NORMAL unless there is reason to do otherwise.

 

 

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Posted

If you set HIGHEST priority when you intend to cancel, that may allow other mods to uncancel it, but at least know it will be cancelled. On the other hand, LOWEST priority allows you to cancel the event without other mods interfering.

Posted

I can't think of many situations where a modder can tolerate to have an event that they are handling cancelled -- I mean you're handling the event for a reason and missing it will presumably break some feature of your mod.  I expect most mods that cancel the event are doing so to prevent the vanilla behavior, not other mods.  I really think most people should want to receive cancelled events -- it can't really hurt and can definitely help.

Basically every situations. In most cases, if a mod cancel an event instance, it does so for a reason, and thus not every time.

Unless you are writing a mod for statistics (number of time something tried to do stuff), you don't need the cancelled event.

It can hurt performance and it can produce weird effects when two mods trigger under the same event.

Posted

Basically every situations. In most cases, if a mod cancel an event instance, it does so for a reason, and thus not every time.

Unless you are writing a mod for statistics (number of time something tried to do stuff), you don't need the cancelled event.

It can hurt performance and it can produce weird effects when two mods trigger under the same event.

 

But why would you assume that another mod's reason for cancelling any more valid than yours?  I think it is all very polite to assume that all other mods are more important than yours, but that doesn't seem very logical to me.

 

Also as I said before, I have to assume they are mostly cancelling in order to prevent vanilla behavior, not necessarily all other mods. 

 

I really don't understand what case where you're handling an event where you could tolerate it being cancelled?  Especially without even knowing it is canceled?  If your mod's functionality is cancelled, you're basically saying your mod might as well not be installed.

 

Certainly, if you know what the other mod is actually doing then you can judge it correctly and decide to defer to their priority and cancellation.  For example maybe they're doing a similar thing to yours, or maybe the result of both handling would be weird.  But resolving that, to me, is a secondary activity related to resolving specific mod compatibility problems.

 

In most cases, if a mod cancel an event instance, it does so for a reason, and thus not every time.

 

I must be missing something about why people cancel events, or specifically "not every time" as you said.  I've mostly done cancellation to fully replace vanilla handling which means handling every time.  Like if I'm changing the rendering in major overhaul, or I'm freezing the motion of an entity, I would cancel the event.

 

And for those events I handle without cancelling, I still expect to handle them every time.  Every time an Item breaks maybe I do something special, or every time and Entity spawns.  I wouldn't want to miss any.  I understand with tick events you can recover if you only get them occasionally, but I'm worried about events that are only single-fire.

 

Can you give me some examples of (non-tick-type) handling where you could tolerate being cancelled without breaking your functionality?

 

It can hurt performance and it can produce weird effects when two mods trigger under the same event.

 

Sure, but resolving weird effects is an activity you do when mod incompatibilities are reported, because it is very situational.  I don't think the general solution to avoiding weird effects is to always let other mods do what they want.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Posted

Maybe I should clarify what I'm trying to generalize.  This is my current thought/development process (incorporating all your feedback above) on whether to receive canceled.

 

1. For tick type (i.e. continuously firing) events, if you can tolerate missing occasional ticks, then just @SubscribeEvent with default settings (normal priority, no received cancelled).  If you can't handle missing them follow the rest of the flow below

 

2. For other event types, default to receiveCanceled=true unless you can tolerate missing some events (in which case just use default settings).  In the handling method for those where you have receiveCanceled=true, check for isCanceled() and output console message if detect a cancelled event.  Otherwise (for now, resolving incompatibility comes later) let your handling code continue even if receiving cancelled event.

 

3. For priority, leave at default unless you're cancelling the event.  If you're cancelling the event, follow the logic that Sequitiri mentions above to figure out if raising or lowering priority makes sense.  (Of course if you have reasons within your own mod to have multiple handlers for same event, you may also want to modify priority to control their order.)

 

4. Test for compatibility with other mods that are common or that you care about running together.  Look for cases (based on your console message) where the other mods are cancelling events.  Modify your code accordingly, ideally in conversation with the other mod's authors. 

 

It just seems to me that going through process where you are aware of the interactions (i.e. due to receiveCanceled=true) is highly preferable to having silent interactions...

 

But there seems to be quite the outcry from expert coders about my suggesting this, so I guess I need to think about it further.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • Version 1.19 - Forge 41.0.63 I want to create a wolf entity that I can ride, so far it seems to be working, but the problem is that when I get on the wolf, I can’t control it. I then discovered that the issue is that the server doesn’t detect that I’m riding the wolf, so I’m struggling with synchronization. However, it seems to not be working properly. As I understand it, the server receives the packet but doesn’t register it correctly. I’m a bit new to Java, and I’ll try to provide all the relevant code and prints *The comments and prints are translated by chatgpt since they were originally in Spanish* Thank you very much in advance No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. MountableWolfEntity package com.vals.valscraft.entity; import com.vals.valscraft.network.MountSyncPacket; import com.vals.valscraft.network.NetworkHandler; import net.minecraft.client.Minecraft; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.animal.Wolf; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.Entity; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.network.PacketDistributor; public class MountableWolfEntity extends Wolf { private boolean hasSaddle; private static final EntityDataAccessor<Byte> DATA_ID_FLAGS = SynchedEntityData.defineId(MountableWolfEntity.class, EntityDataSerializers.BYTE); public MountableWolfEntity(EntityType<? extends Wolf> type, Level level) { super(type, level); this.hasSaddle = false; } @Override protected void defineSynchedData() { super.defineSynchedData(); this.entityData.define(DATA_ID_FLAGS, (byte)0); } public static AttributeSupplier.Builder createAttributes() { return Wolf.createAttributes() .add(Attributes.MAX_HEALTH, 20.0) .add(Attributes.MOVEMENT_SPEED, 0.3); } @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { ItemStack itemstack = player.getItemInHand(hand); if (itemstack.getItem() == Items.SADDLE && !this.hasSaddle()) { if (!player.isCreative()) { itemstack.shrink(1); } this.setSaddle(true); return InteractionResult.SUCCESS; } else if (!level.isClientSide && this.hasSaddle()) { player.startRiding(this); MountSyncPacket packet = new MountSyncPacket(true); // 'true' means the player is mounted NetworkHandler.CHANNEL.sendToServer(packet); // Ensure the server handles the packet return InteractionResult.SUCCESS; } return InteractionResult.PASS; } @Override public void travel(Vec3 travelVector) { if (this.isVehicle() && this.getControllingPassenger() instanceof Player) { System.out.println("The wolf has a passenger."); System.out.println("The passenger is a player."); Player player = (Player) this.getControllingPassenger(); // Ensure the player is the controller this.setYRot(player.getYRot()); this.yRotO = this.getYRot(); this.setXRot(player.getXRot() * 0.5F); this.setRot(this.getYRot(), this.getXRot()); this.yBodyRot = this.getYRot(); this.yHeadRot = this.yBodyRot; float forward = player.zza; float strafe = player.xxa; if (forward <= 0.0F) { forward *= 0.25F; } this.flyingSpeed = this.getSpeed() * 0.1F; this.setSpeed((float) this.getAttributeValue(Attributes.MOVEMENT_SPEED) * 1.5F); this.setDeltaMovement(new Vec3(strafe, travelVector.y, forward).scale(this.getSpeed())); this.calculateEntityAnimation(this, false); } else { // The wolf does not have a passenger or the passenger is not a player System.out.println("No player is mounted, or the passenger is not a player."); super.travel(travelVector); } } public boolean hasSaddle() { return this.hasSaddle; } public void setSaddle(boolean hasSaddle) { this.hasSaddle = hasSaddle; } @Override protected void dropEquipment() { super.dropEquipment(); if (this.hasSaddle()) { this.spawnAtLocation(Items.SADDLE); this.setSaddle(false); } } @SubscribeEvent public static void onServerTick(TickEvent.ServerTickEvent event) { if (event.phase == TickEvent.Phase.START) { MinecraftServer server = net.minecraftforge.server.ServerLifecycleHooks.getCurrentServer(); if (server != null) { for (ServerPlayer player : server.getPlayerList().getPlayers()) { if (player.isPassenger() && player.getVehicle() instanceof MountableWolfEntity) { MountableWolfEntity wolf = (MountableWolfEntity) player.getVehicle(); System.out.println("Tick: " + player.getName().getString() + " is correctly mounted on " + wolf); } } } } } private boolean lastMountedState = false; @Override public void tick() { super.tick(); if (!this.level.isClientSide) { // Only on the server boolean isMounted = this.isVehicle() && this.getControllingPassenger() instanceof Player; // Only print if the state changed if (isMounted != lastMountedState) { if (isMounted) { Player player = (Player) this.getControllingPassenger(); // Verify the passenger is a player System.out.println("Server: Player " + player.getName().getString() + " is now mounted."); } else { System.out.println("Server: The wolf no longer has a passenger."); } lastMountedState = isMounted; } } } @Override public void addPassenger(Entity passenger) { super.addPassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(true)); } } } @Override public void removePassenger(Entity passenger) { super.removePassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is no longer mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(false)); } } } @Override public boolean isControlledByLocalInstance() { Entity entity = this.getControllingPassenger(); return entity instanceof Player; } @Override public void positionRider(Entity passenger) { if (this.hasPassenger(passenger)) { double xOffset = Math.cos(Math.toRadians(this.getYRot() + 90)) * 0.4; double zOffset = Math.sin(Math.toRadians(this.getYRot() + 90)) * 0.4; passenger.setPos(this.getX() + xOffset, this.getY() + this.getPassengersRidingOffset() + passenger.getMyRidingOffset(), this.getZ() + zOffset); } } } MountSyncPacket package com.vals.valscraft.network; import com.vals.valscraft.entity.MountableWolfEntity; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class MountSyncPacket { private final boolean isMounted; public MountSyncPacket(boolean isMounted) { this.isMounted = isMounted; } public void encode(FriendlyByteBuf buffer) { buffer.writeBoolean(isMounted); } public static MountSyncPacket decode(FriendlyByteBuf buffer) { return new MountSyncPacket(buffer.readBoolean()); } public void handle(NetworkEvent.Context context) { context.enqueueWork(() -> { ServerPlayer player = context.getSender(); // Get the player from the context if (player != null) { // Verifies if the player has dismounted if (!isMounted) { Entity vehicle = player.getVehicle(); if (vehicle instanceof MountableWolfEntity wolf) { // Logic to remove the player as a passenger wolf.removePassenger(player); System.out.println("Server: Player " + player.getName().getString() + " is no longer mounted."); } } } }); context.setPacketHandled(true); // Marks the packet as handled } } networkHandler package com.vals.valscraft.network; import com.vals.valscraft.valscraft; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.network.NetworkRegistry; import net.minecraftforge.network.simple.SimpleChannel; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class NetworkHandler { private static final String PROTOCOL_VERSION = "1"; public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( new ResourceLocation(valscraft.MODID, "main"), () -> PROTOCOL_VERSION, PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals ); public static void init() { int packetId = 0; // Register the mount synchronization packet CHANNEL.registerMessage( packetId++, MountSyncPacket.class, MountSyncPacket::encode, MountSyncPacket::decode, (msg, context) -> msg.handle(context.get()) // Get the context with context.get() ); } }  
    • Do you use features of inventory profiles next (ipnext) or is there a change without it?
    • Remove rubidium - you are already using embeddium, which is a fork of rubidium
  • Topics

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.