Jump to content

[1.7.10] Parameterized Interfaces, TileEntities, and Event subscriptions.


Scribblon

Recommended Posts

This is a feasibility question, there is not much code yet. I am sure the route I am taking is possible, but as someone who likes to write as little code in the final implementation, but overwrites to get a solid generalized framework going... This thread is going to be about generalized/parameterized interfaces in the EVENT_BUS.

 

Background: Skip to the line if you do not care.

I am working on a mod which is focussed on balanced teleportation. This 'balanced' aspect includes many things. But I aim it to be easily accessible in the early game. Teleportation takes time and isn't instant. To get to the mid-game of the mod you need quite some resources. But when you reach it there are ways to create more of the same rather and in a put-resources-in-and-forget fashion. And towards the end-game you need to worry about an energy system (I can hear the cringes >:] , but yeah it is mostly in the end-game this is needed). And... it is inspired by the tears of Bioshock Infinite. So lots of tear-static noises, icons with static noise backgrounds, and the player being in two places at the same time. Probability Space, but also adds the Unmaking, and Void Space.

But most of all, the biggest aspect is that a player can defend himself against unwanted incoming rifts as soon as the player found redstone and its first ender-shard. The players can place crafted blocks which will 'push away' or 'attract' the incoming rift. Allowing the player to set-up traps, force players to teleport inside walls etc etc.


I understand I could do this in many ways. I could request the chunk data and check the TileEntity List there. Or iterate through the whole 'Loaded TileEntity'-list searching for my blocks whenever someone tries to teleport. But to think ALL tile-entities needed to be iterated through and `instanceof`-ed and casted. Makes me cringe when I think of the tubing in someones average base would take... So, I thought of the following: All TileEntities who should react to a player teleporting in, subscribes to a RiftFormationEvent. This way the iteration is only done upon loading of the TileEntity, and only on chunk-level. And whenever they are already loaded the event will make sure the right TileEntities will be invoked and no iteration of TileEntity lists (in my mod) is required.

It seems I was wrong about some things concerning TileEntity loading on chunk-level. They aren't loaded in like that, unlike normal Entities... Still I plan on doing the event subscription like I made known. The question remains.

And again it seems I was wrong. There IS a Map containing TileEntities and this is in chunk-sized (each chunk has its own). It is just that it is by no means clear what is stored inside this. On declaration it just makes a new HashMap (Assuming <Object, Object> it becomes clear that the Key Value is a ChunkPosition Object).

 

-Player initiates teleport.
-Chunk is force loaded (if it isn't loaded already) where exitEntity should spawn.
    (>Chunk.Load event is fired and subscribes TileEntities <<<<)
-Chunk checks NBTData for possible interactions from neighboring chunks (Stored in chunk NBT). If true > 
    >Chunk.Load event is fired and subscribes TileEntities <<<<
(Repeat on a breath-First approach with a maximum depth of a TBD number. Currently it depends on what has been found so far. When it is an end-game disruptor it will break of the search and use that as the main force. When it is a network of simple disruption stones, I think 3 x 3 chunks should be the max... But that is subject to change)
-RiftFormationEvent is fired.
    >All subscribed TileEntities will check if ExitEntity is in range. If so, it will register its own displacement vector in the event. If not, nothing will happen.
    >All displacements are calculated to the final destination of the ExitEntity
-ExitEntity is spawned
-EnterEntity is spawned
-Player enters ProbabilitySpace
-If both entities remain undisturbed when the player reaches his exit-gate. Player rifts through. (Currently the Player doesn't move on its own, and is more forced to go through an 'animated progress bar' of some sort.)
     If not, player will be thrown back to his start location with a bunch of damage... Or thrown into the Void (A dimension, not the one below the world). RnGesus (or the config option) will decide the players fate.
-Ticket of the forced chunks is invalidated.

The current points I am working at is notated with the "<<<<"-pointers.

 

As far as I understand the Event system in Forge is that it will write a class on the fly which will call-back on the function you subscribe it to. But the problem with TileEntities is that they aren't always there, so I expect there to be problems whenever I leave a TileEntity subscribed. In the WorldManager who tries to unload an entity which is still referenced in an other part of the program. Or when the Event is fired and tries to invoke a subscribed method which isn't loaded in the memory.

So, I have a ChunkEvent handler which subscribes and unsubscribes these Entities.

 

To prevent writing a massive method to subscribe and unsubscribe every possible class event type combination, or go with a subscription system where all TileEntities are subscribed to the root-event-type of my mod and cast it to whatever is needed,  I thought of the the following:

I made a parameterized interface:

package nl.scribblon.riftcraft.util;

import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import nl.scribblon.riftcraft.event.RiftCraftEvent;

/**
* Created by Scribblon for RiftCraft.
* Date Creation: 04-09-14
*
* Interface which needs to be implemented to whenever a TileEntity wants to be subscribed upon Chunk.Load.
*/
public interface ISubscribeable<T extends RiftCraftEvent> {

    void onEvent(T t);

}

The `@SubscribeEvent` will be placed in the implementation.

 

When implementing this interface it will look like this:

 

public class DisruptionStone implements ISubsribeable<RiftFormationEvent> {
    
    @SubscribeEvent
    public void onEvent(RiftFormationEvent event) {
        // do stuff
    }

}

 

Now here I start to enter a zone where I am questioning my own knowledge of java and how this interacts with the EVENT_BUS#register(...)-method. I have no clue how the ClassWriter inside the EventBus will interpret my interface. Or even how the ClassWriter does its magic. (It can be found in the ASMEventHandler)

 

When I implement ISubscribable I must give the interface a Type. T will be for example a RiftFormationEvent. However, upon loading the Chunk it will be passed on to the EVENT_BUS#register(iSubscribed) as the generalized interface-form.

 

Will the ClassWriter interpret the interface as it solved Parameter-Type? Or will it pick the RiftCraftEvent as the event type as it is the root or will all go well and I am just worrying about nothing?

 

I hope someone knows, as Google and my Google-Fu has forsaken me.

 

I am always open for suggestions, improvements, etc.

 

EDIT: see strikethrough section.

"I guess as this is pretty much WIP, I can just 'borrow' the sounds from that game without problem. They are just 'placeholders' anyway." -Me, while extracting the Tear sounds of Bioshock Infinite.

Link to comment
Share on other sites

First of all: registering EventHandlers outside of the mod-loading phase (preInit, etc.) will trigger a log entry with Level.ERROR.

Ah, I wasn't aware. Well that rules out the TileEntity listens to the Events way I thought was the way.

 

Regarding your interface: It is useless, this is not how the EventBus works! You can put whatever methods you want, you can name them however you want. If you pass any object to the EventBus it will find any method in there that's annotated, whether it's implemented from an interface (that might actually cause problems, looking at the code...) or not.

I am aware that the EventBus accepts any method name as long the name is accepted by the Java-compiler. But it always expect the method to have only 1 parameter, and this parameter should be a SubClass of the cpw.mods.fml.common.eventhandler.Event - class.

 

It is that I actually never figured out how interfaces work at the low-end-level of Java. How is an object which implemented an interface presented to other objects. Will the Interface reference to the actual object making the ClassWriter read the right method with the correct Event Type. Or will the object stored as interface be reference to its own kind of Object which has only that interfaced (generalized) method available and has the real implementation masked as a Java-Compiled-Code-Fu-Reference underneath it.

This does not mean that I don't know how they work and help in polymorphism on the high-end side of the code, what I am trying to achieve here.

 

TileEntities actually have a built-in chunk-load and unload handler, validate() and invalidate(). But I still think that you should not be registering every single TileEntity as an EventHandler, that would cause quite some overhead. Have a centralized handler that delegates to the TileEntities.

That explains why I couldn't find the .onChunkLoad() equivalent of the .onChunkUnload() method in TileEntity.

But analyzing these methods leaves me wondering why .validate() is only used in two classes: BlockFurnace and Chunk.

In chunk it is called in the obfuscated func_150812_a . Which is called in World#setTileEntity() and Chunk#addTileEntity()... Going deeper only makes it a confusing looping dependency mess back to Chunk, deeper again back to World?!

 

Still, if I were to make my own manager with a list that listens to this event and delegate it to the whole registered list whenever this event is fired. I think the overhead will be about the same. Now I am registering to a Manager-class instead of the EventBus. The EventBus has already its own overhead when posting and registering an Event to the manager. And then you also got the Manager with its own Collection implementation to iterate it through.

But yeah, I do not understand the EventBus well enough to know if this implementation would be worse or not. And it seems it is the only way since my original plan is already out the water. It does lack the ChunkHandler though if I can just use the TileEntites own unload/load implementations.

"I guess as this is pretty much WIP, I can just 'borrow' the sounds from that game without problem. They are just 'placeholders' anyway." -Me, while extracting the Tear sounds of Bioshock Infinite.

Link to comment
Share on other sites

An interface method is basically like an public abstract class method in an abstract class. It is a "method stub" in the abstract class, with no body, and in the subclass, where it is implemented it is a normal public method. Methods that override something are not any different from methods that do not override something. @Override for example does not exist in the bytecode at all.

 

Ah... Wait, I see the massive error in the example-Class. It missed the @Override-annotation. But if you say it was not going to be called the way I thought it would, it wouldn't have mattered anyway.

 

But like I said, I do know what interfaces are on how they work on the high end. It is the actual compiled low end I am not aware of how interfaces work and interact with each other. If what you have just typed is how it works, I think I need to delve deeper into that.

 

Basically validate is called when the TE is added to the Chunk and invalidate is called when, well, it is no longer valid because another Block has been placed in it's location.

But what happens when a chunk is loaded? It is loading in the chunk... It is not setting blocks to be TileEntities or at least 6 'usage analysis'-s deep it seems to never be touched by something that loads in the TileEntity. There is only one way I guess I can test this. Just try it. And if that fails, back to the drawing board I must go.

 

The EventBus is really blazing fast. When an Event is fired, the list of listeners for that Event are attached to the event itself, so there is no Map lookup for the listener list or anything, just a single method invocation. The ListenerList itself is just an Array which is iterated over (also damn fast). Then each EventListener is a class that is compiled on the fly when you register your object to directly invoke your methods, so there is no reflection overhead either.

So really, there is basically no overhead at all to the event system. What determines the speed, is your code, not the EventBus.

That explains the overhead it would have created whenever I would just add and delete these methods of different TileEntities on a Load/Unload basis. Guess modifying these files is a bit more of a hassle than adding and/or removing it from a Collection-implementation.

And yes, if I was to go ahead with my plan without consulting. It would probably made the EventBus slow whenever a lot of Entities needed to be written in and out of the resulting Event-Super-Class. On the regular tick by tick basis it wouldn't have mattered if I understand it now.

But I gotta say, it contradicts a bit what you said earlier. If an EventBus would have no overhead, adding complexity to the EventBus would result in no overhead either as it stands would have no overhead to begin with. It is that the native EventBus code isn't coded to handle registration and deletion of listeners beyond the Post-Init stage. But that is how Forge/FML decided to implement the Event Driven Architecture.

 

In the end I came up with the following:

I have an EventManager which contains a Collection (Type pending, EnumMap<EventType, HashSet<ISubsribeable<? extends RiftCraftEvent>> is currently the implementation I am heading for). In which I can register the ISubscribable interface implementations.

Whenever an event is fired concerning my mod, the Manager will catch it and deligate it to the right Set, where it will iterate through the registered TileEntities.

The registering and unregistering of TileEntities is done from the .invalidate(), validate() methods in the TileEntity. Currently I am not able to test this yet, so I keep my ChunkHandler around whenever I am actually able to test a rift. (I would love to be able to write JUnit tests for these kind of things... It only is not possible as it requires to interact with Minecraft-code itself to see how it loads and unloads Tile-Entities.)

 

NEVERTHELESS, thanks for the info and the help.

"I guess as this is pretty much WIP, I can just 'borrow' the sounds from that game without problem. They are just 'placeholders' anyway." -Me, while extracting the Tear sounds of Bioshock Infinite.

Link to comment
Share on other sites

The EnumMap is a good choice, but I wouldn't go for HashSet. Use a simple ArrayList. That's much faster for iteration (and anything else pretty much, at least the operations you're gonna need here).

 

I would agree with you if I weren't going to add and remove elements in the ArrayList upon loading and unloading of TileEntities.

Dynamic Arrays would have an O(n) on insertion and deletion. While HashSet has a O(1) on those.

Both implementations are equal in providing iterators and for me the order in which the iterator presents all stored elements don't need to be in a particular order.

 

In all other cases, I would go for an ArrayList. Even though I know these Big O notations only becomes a thing on large element numbers. I think I can still justify it here on the basis of the expected amount of insertions and deletions in the Collection.

 

http://bigocheatsheet.com/

"I guess as this is pretty much WIP, I can just 'borrow' the sounds from that game without problem. They are just 'placeholders' anyway." -Me, while extracting the Tear sounds of Bioshock Infinite.

Link to comment
Share on other sites

I will report back when I am at the stage where I can benchmark this. (And I remember to check my //TODO list from time to time)

Currently I am finalizing the ground work for the implementation of the items. Last thing to come are the renders and the textures... I am pretty much a programmer over artist -_-'

"I guess as this is pretty much WIP, I can just 'borrow' the sounds from that game without problem. They are just 'placeholders' anyway." -Me, while extracting the Tear sounds of Bioshock Infinite.

Link to comment
Share on other sites

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



×
×
  • Create New...

Important Information

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