Current state is still in development
#####Preface By this point, you should already understand the object structure and concept of Causes and EventContexts. If you have not, do consider reading that aspect prior to reading this section as this section is about the implementation that powers creating these objects for all events.
Topics required to understand this section include but are not limited to:
- Causes
- Stacks (Data structure)
- Lists (Data structure)
- MultiMaps (Yet another data structure)
- Mixins (Sponge topic)
#####If you already know about stacks and understand Java stack traces and how Java is a stack based system, skip to the next section Consider for a moment, you write a simple plugin and in your listener, you have an exception, usually what helps you understand where the exception came from, Java provides you with a stacktrace. Fortunately for you, the smart developer you are, can decipher a stack trace and understand that at the top of the stack trace is the culprit for the exception to be thrown! Now, how did Java get to tell you about this? Well, Java uses a stack based process so that when a method calls another method, method 1 is pushed onto the stack (for being called first), and then method2 is pushed onto the top of the stack for being called, so in the "stack" you'd have:
method2()
method1()
bunchOfJavaNative()
javaLaunchWhatever()
Fortunately, we can derive that stack traces basically tell us who and where an exception is caused, and the "trace" of how we got there. Now, imagine that we started applying this "call stack" to Minecraft to trace who is doing what.
Well, I did say imagine, didn't I? If we were to consider for a second, that since Minecraft pretty much
runs everything on the "server thread", we can consider it's in a constant global state of going back and
forth between ticking a World
, which will tick Entities
, TileEntities
, and Block
s, each of which
has a potential to do something, we can start to consider that Minecraft is yet another stack of things
that we can trace. Now if only we could actually trace who is doing what and react accordingly....
Ok, I know, I'm getting to the point, just have to break down what literally powers all events in Sponge to this doc, so forgive me if I'm writing for everyone to be able to understand... Anyways, the reason why we talk about treating Minecraft as a stack and tracing who is doing what is that we literally do just that.
To show you, I have to present three classes to you:
somehow make these classes foldable or something?
PhaseTracker
IPhaseState
PhaseContext
public final class PhaseTracker {
private static final PhaseTracker INSTANCE = new PhaseTracker();
private PhaseTracker() {
}
public static PhaseTracker getInstance() {
return INSTANCE;
}
public PhaseData getCurrentPhaseData() {
return this.stack.peek();
}
public IPhaseState<?> getCurrentState() {
return this.stack.peekState();
}
public PhaseContext<?> getCurrentContext() {
return this.stack.peekContext();
}
void switchToPhase(IPhaseState<?> state, PhaseContext<?> phaseContext) {
// something something something dark side
// sure, could show the actual implementation, but what's the point of that?
this.stack.push(state, phaseContext);
}
public void completePhase(IPhaseState<?> prevState) {
// Lots of code here, but we'll go over it a little later.
}
}
In a sense, PhaseTracker
is what is usually referred to when it comes to Sponge's tracking system, since
literally everything flows through it. It's a static global state stack manager in a few words, and
there's about three methods (really two) that are ever used: getCurrentState()
and getCurrentContext()
.
he other two methods are realistically internalized to the IPhaseState
s own self closing selves (which I'll
talk about later). What PhaseTracker
can be summarised as: It has a stack of IPhaseState
s and their
respective PhaseContext
s to where each IPhaseState
is processed when it is being "popped".
To give some understanding of IPhaseState
, it's the equivalent to a literal GameState
for the game state events
where we "know" something is happening, like when an Entity
is ticking. We achieve this "state" of knowing
by using Mixins where they are necessary.
To give you the specific example of how we know an Entity
is ticking, we (developers) already know that
Minecraft ticks each World
, which ticks each Entity
currently loaded. So, if we write the following mixin:
@Mixin(net.minecraft.world.World.class)
public class MixinWorld {
@Redirect(method = "updateEntityWithOptionalForce", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;onUpdate()V"))
protected void onCallEntityUpdate(net.minecraft.entity.Entity entity) {
entity.onUpdate(); // We can call the same method
}
}
Sorry, I got a little ahead of myself.... We have to discuss the case where IPhaseState
s are very specific, if we
want to say that an Entity
is ticking, we have to know that the current IPhaseState
is a specific state, right?
Well, we can create static singleton states, like EntityTickPhaseState
and have a singular reference to
said state, which we do in TickPhase
.
This new EntityTickPhaseState
is neat and all, but we need to actually discuss PhaseContext
a little to give some
understanding before I proceed.
PhaseContext
is a rudimentary "state context". Take our first example of your method1()
calling method2()
with
specific arguments, and if one of the arguments is known to cause issues, you'd print out a warning about the
argument, right? Right. That's traditional "warn that the provided inputs are not valid and exit safely". We would
like to be able to harness the ability to say that "hey, we know which entity is ticking during this IPhaseState
."
And this is where we can start using PhaseContext
to literally store whatever we need during a IPhaseState
, for
later retrieval during the current state, such as when we're throwing events....
How do we go from a PhaseContext
to events? The whole point of this topic is to explain how we implement our events
and their causes, this is it. Since the PhaseContext
stores some information for us (by our own design, not magically),
we can retrieve that information as long as it is relevant to whatever we need to do at the time.
So, if I'm throwing an event and PhaseTracker.getInstance().getCurrentState()
returns an instance of
EntityTickPhaseState
, we know that there would be an Entity
as the source for ticking, and therefor can do the following:
public void someMethodCalledDuringATick() {
if (PhaseTracker.getInstance().getCurrentState() instanceof EntityTickPhaseState) {
final Entity entity = PhaseTracker.getInstance().getCurrentContext().getSource(Entity.class);
Sponge.getCauseStackManager().pushCause(entity);
// do something that will throw an event, or throw your own event, and use the CauseStackManager to get your current cause
}
}
Yep, the states already know what they are assigned to do, and when to do them, but it's not as hard coded as
the above example, it's a little more streamlined than that. Since Sponge has to remain super abstract about
most things, Sponge will delegate a majority of processing to the actual IPhaseState
, which essentially writes
it's own contract on what should be available, made available, and how to process whatever is being captured, performed,
thrown, or changed.