-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add priority to event prefilters. (#1378)
This allows for prefilters to be processed in order of least to highest complexity, leading to more efficient processing of events that don't match. For instance, matching a regex is more expensive than matching a boolean, so we check the boolean first, and if it doesn't match, then we don't try the regex match, whether or not it would match. General prefilter types have been given a default complexity, but as every prefilter is different, it's also possible for individual overrides to provide a custom priority instead.
- Loading branch information
1 parent
6279947
commit 416a628
Showing
28 changed files
with
588 additions
and
386 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
333 changes: 5 additions & 328 deletions
333
src/main/java/com/laytonsmith/core/events/AbstractEvent.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,334 +1,11 @@ | ||
package com.laytonsmith.core.events; | ||
|
||
import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; | ||
import com.laytonsmith.PureUtilities.Common.StreamUtils; | ||
import com.laytonsmith.abstraction.MCCommandSender; | ||
import com.laytonsmith.abstraction.MCPlayer; | ||
import com.laytonsmith.abstraction.events.MCPlayerEvent; | ||
import com.laytonsmith.annotations.core; | ||
import com.laytonsmith.annotations.hide; | ||
import com.laytonsmith.core.Documentation; | ||
import com.laytonsmith.core.LogLevel; | ||
import com.laytonsmith.core.MSLog; | ||
import com.laytonsmith.core.MSLog.Tags; | ||
import com.laytonsmith.core.MethodScriptCompiler; | ||
import com.laytonsmith.core.ParseTree; | ||
import com.laytonsmith.core.Static; | ||
import com.laytonsmith.core.constructs.CArray; | ||
import com.laytonsmith.core.constructs.CNull; | ||
import com.laytonsmith.core.constructs.Target; | ||
import com.laytonsmith.core.environments.CommandHelperEnvironment; | ||
import com.laytonsmith.core.environments.Environment; | ||
import com.laytonsmith.core.environments.StaticRuntimeEnv; | ||
import com.laytonsmith.core.events.prefilters.Prefilter; | ||
import com.laytonsmith.core.events.prefilters.PrefilterBuilder; | ||
import com.laytonsmith.core.exceptions.CRE.CREFormatException; | ||
import com.laytonsmith.core.exceptions.CancelCommandException; | ||
import com.laytonsmith.core.exceptions.ConfigRuntimeException; | ||
import com.laytonsmith.core.exceptions.EventException; | ||
import com.laytonsmith.core.exceptions.FunctionReturnException; | ||
import com.laytonsmith.core.exceptions.PrefilterNonMatchException; | ||
import com.laytonsmith.core.exceptions.ProgramFlowManipulationException; | ||
import com.laytonsmith.core.natives.interfaces.Mixed; | ||
import com.laytonsmith.core.profiler.ProfilePoint; | ||
|
||
import java.net.URL; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
/** | ||
* This helper class implements a few of the common functions in event, and most (all?) Events should extend this class. | ||
* | ||
* This class is deprecated, and events should extend AbstractGenericEvent directly instead. | ||
* @deprecated Use {@link AbstractGenericEvent} instead. | ||
*/ | ||
public abstract class AbstractEvent implements Event, Comparable<Event> { | ||
|
||
private EventMixinInterface mixin; | ||
// protected EventHandlerInterface handler; | ||
// | ||
// protected AbstractEvent(EventHandlerInterface handler){ | ||
// this.handler = handler; | ||
// } | ||
// | ||
|
||
public final void setAbstractEventMixin(EventMixinInterface mixin) { | ||
this.mixin = mixin; | ||
} | ||
|
||
/** | ||
* This function should return true if the event code should be run, based on implementation specific conditions | ||
* for the BindableEvent. | ||
* | ||
* @param e The bindable event itself | ||
* @return {@code true} if the event code should be run | ||
*/ | ||
public boolean shouldFire(BindableEvent e) { | ||
return this.mixin.shouldFire(e); | ||
} | ||
|
||
/** | ||
* If the event needs to run special code when a player binds the event, it can be done here. By default, an | ||
* UnsupportedOperationException is thrown, but is caught and ignored. | ||
*/ | ||
@Override | ||
public void bind(BoundEvent event) { | ||
|
||
} | ||
|
||
/** | ||
* If the event needs to run special code when a player unbinds the event, it can be done here. By default, an | ||
* UnsupportedOperationException is thrown, but is caught and ignored. | ||
*/ | ||
@Override | ||
public void unbind(BoundEvent event) { | ||
|
||
} | ||
|
||
/** | ||
* If the event needs to run special code at server startup, it can be done here. By default, nothing happens. | ||
*/ | ||
@Override | ||
public void hook() { | ||
|
||
} | ||
|
||
@Override | ||
public boolean matches(Map<String, Mixed> prefilter, BindableEvent e) throws PrefilterNonMatchException { | ||
throw new UnsupportedOperationException(); | ||
} | ||
|
||
/** | ||
* This function is run when the actual event occurs. | ||
* | ||
* @param tree The compiled parse tree | ||
* @param b The bound event | ||
* @param env The operating environment | ||
* @param activeEvent The active event being executed | ||
*/ | ||
@Override | ||
public final void execute(ParseTree tree, BoundEvent b, Environment env, BoundEvent.ActiveEvent activeEvent) throws ConfigRuntimeException { | ||
preExecution(env, activeEvent); | ||
|
||
// Events can have a player to put into the CommandHelperEnvironment. | ||
if(activeEvent.getUnderlyingEvent() instanceof MCPlayerEvent playerEvent) { | ||
env.getEnv(CommandHelperEnvironment.class).SetPlayer(playerEvent.getPlayer()); | ||
} else { | ||
// Probably not a player event, but might still have a player context. | ||
Mixed c = activeEvent.getParsedEvent().get("player"); | ||
if(c != null) { | ||
if(c instanceof CNull) { | ||
// This is a CNull "player", likely from an entity event, so we need to ensure player() does | ||
// not return a player inherited from the bind's parent environment. | ||
if(env.getEnv(CommandHelperEnvironment.class).GetPlayer() != null) { | ||
env.getEnv(CommandHelperEnvironment.class).SetPlayer(null); | ||
} | ||
} else { | ||
MCCommandSender p = Static.getServer().getPlayer(c.val()); | ||
if(p == null) { | ||
// Check if event (possibly from an extension) injected the player but didn't extend MCPlayerEvent | ||
p = Static.GetInjectedPlayer(c.val()); | ||
} | ||
if(p != null) { | ||
env.getEnv(CommandHelperEnvironment.class).SetPlayer((MCPlayer) p); | ||
} else { | ||
MSLog.GetLogger().w(Tags.GENERAL, c.val() + " offline for " + b.getEventName(), tree.getTarget()); | ||
// Set env CommandSender to prevent incorrect inherited player from being used in a player event. | ||
if(env.getEnv(CommandHelperEnvironment.class).GetPlayer() != null) { | ||
env.getEnv(CommandHelperEnvironment.class).SetPlayer(null); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
ProfilePoint event = null; | ||
if(env.getEnv(StaticRuntimeEnv.class).GetProfiler() != null) { | ||
event = env.getEnv(StaticRuntimeEnv.class).GetProfiler().start( | ||
"Event " + b.getEventName() + " (defined at " + b.getTarget().toString() + ")", LogLevel.ERROR); | ||
} | ||
try { | ||
try { | ||
MethodScriptCompiler.execute(tree, env, null, null); | ||
} catch (CancelCommandException ex) { | ||
if(ex.getMessage() != null && !ex.getMessage().isEmpty()) { | ||
StreamUtils.GetSystemOut().println(ex.getMessage()); | ||
} | ||
} catch (FunctionReturnException ex) { | ||
//We simply allow this to end the event execution | ||
} catch (ProgramFlowManipulationException ex) { | ||
ConfigRuntimeException.HandleUncaughtException(new CREFormatException("Unexpected control flow operation used.", ex.getTarget()), env); | ||
} | ||
} finally { | ||
if(event != null) { | ||
event.stop(); | ||
} | ||
// Finally, among other things, we need to clean-up injected players and entities | ||
postExecution(env, activeEvent); | ||
} | ||
} | ||
|
||
/** | ||
* This method is called before the event handling code is run, and provides a place for the event code itself to | ||
* modify the environment or active event data. | ||
* | ||
* @param env The environment, at the time just before the event handler is called. | ||
* @param activeEvent The event handler code. | ||
*/ | ||
public void preExecution(Environment env, BoundEvent.ActiveEvent activeEvent) { | ||
|
||
} | ||
|
||
/** | ||
* This method is called after the event handling code is run, and provides a place for the event code itself to | ||
* modify or cleanup the environment or active event data. | ||
* | ||
* @param env The environment, at the time just before the event handler is called. | ||
* @param activeEvent The event handler code. | ||
* @throws UnsupportedOperationException If the preExecution isn't supported, this may be thrown, and it will be | ||
* ignored. | ||
*/ | ||
public void postExecution(Environment env, BoundEvent.ActiveEvent activeEvent) { | ||
|
||
} | ||
|
||
/** | ||
* For sorting and optimizing events, we need a comparison operation. By default it is compared by looking at the | ||
* event name. | ||
* | ||
* @param o | ||
* @return | ||
*/ | ||
@Override | ||
public int compareTo(Event o) { | ||
return this.getName().compareTo(o.getName()); | ||
} | ||
|
||
/** | ||
* Since most events are minecraft events, we return true by default. | ||
* | ||
* @return | ||
*/ | ||
@Override | ||
public boolean supportsExternal() { | ||
return true; | ||
} | ||
|
||
/** | ||
* If it is ok to by default do a simple conversion from a CArray to a Map, this method can do it for you. Likely | ||
* this is not acceptable, so hard-coding the conversion will be necessary. | ||
* | ||
* @param manualObject | ||
* @return | ||
*/ | ||
public static Object DoConvert(CArray manualObject) { | ||
Map<String, Mixed> map = new HashMap<>(); | ||
for(String key : manualObject.stringKeySet()) { | ||
map.put(key, manualObject.get(key, Target.UNKNOWN)); | ||
} | ||
return map; | ||
} | ||
|
||
public Map<String, Mixed> evaluate_helper(BindableEvent e) throws EventException { | ||
return mixin.evaluate_helper(e); | ||
} | ||
|
||
/** | ||
* By default, this function triggers the event by calling the mixin handler. If this is not the desired behavior, | ||
* this method can be overridden in the actual event (if it's an external event, for instance) | ||
* | ||
* @param o | ||
*/ | ||
@Override | ||
public void manualTrigger(BindableEvent o) { | ||
mixin.manualTrigger(o); | ||
} | ||
|
||
@Override | ||
public void cancel(BindableEvent o, boolean state) { | ||
mixin.cancel(o, state); | ||
} | ||
|
||
@Override | ||
public boolean isCancellable(BindableEvent o) { | ||
return mixin.isCancellable(o); | ||
} | ||
|
||
@Override | ||
public boolean isCancelled(BindableEvent o) { | ||
return mixin.isCancelled(o); | ||
} | ||
|
||
@Override | ||
public URL getSourceJar() { | ||
return ClassDiscovery.GetClassContainer(this.getClass()); | ||
} | ||
|
||
/** | ||
* Returns true if the event is annotated with @hide | ||
* | ||
* @return | ||
*/ | ||
@Override | ||
public final boolean appearInDocumentation() { | ||
return this.getClass().getAnnotation(hide.class) != null; | ||
} | ||
|
||
private static final Class[] EMPTY_CLASS = new Class[0]; | ||
|
||
@Override | ||
public Class<? extends Documentation>[] seeAlso() { | ||
return EMPTY_CLASS; | ||
} | ||
|
||
/** | ||
* Most events should return true for this, but passive events may override this to return null. | ||
* | ||
* @return | ||
*/ | ||
@Override | ||
public boolean addCounter() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public final boolean isCore() { | ||
Class c = this.getClass(); | ||
do { | ||
if(c.getAnnotation(core.class) != null) { | ||
return true; | ||
} | ||
c = c.getDeclaringClass(); | ||
} while(c != null); | ||
return false; | ||
} | ||
|
||
private Map<String, Prefilter<? extends BindableEvent>> prefilterCache = null; | ||
private volatile boolean isCacheSaturated = false; | ||
|
||
@Override | ||
public final Map<String, Prefilter<? extends BindableEvent>> getPrefilters() { | ||
if(!isCacheSaturated) { | ||
synchronized(this) { | ||
if(!isCacheSaturated) { | ||
PrefilterBuilder builder = getPrefilterBuilder(); | ||
if(builder != null) { | ||
prefilterCache = builder.build(); | ||
} | ||
isCacheSaturated = true; | ||
} | ||
} | ||
} | ||
return prefilterCache; | ||
} | ||
|
||
/** | ||
* Returns the prefilter builder for this subclass. This is built by AbstractEvent and cached, so that calls | ||
* to getPrefilters will be faster for future calls. | ||
* @return | ||
*/ | ||
// TODO: Once everything has this, this should be re-added, since everything should have its own version. | ||
// @ForceImplementation | ||
protected PrefilterBuilder getPrefilterBuilder() { | ||
return null; | ||
} | ||
@Deprecated | ||
public abstract class AbstractEvent | ||
extends AbstractGenericEvent<BindableEvent> { | ||
|
||
} |
Oops, something went wrong.