Skip to content

Commit

Permalink
Add priority to event prefilters. (#1378)
Browse files Browse the repository at this point in the history
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
LadyCailin authored Mar 5, 2024
1 parent 6279947 commit 416a628
Show file tree
Hide file tree
Showing 28 changed files with 588 additions and 386 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import com.laytonsmith.core.Static;
import com.laytonsmith.core.constructs.CString;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.core.events.AbstractEvent;
import com.laytonsmith.core.events.AbstractGenericEvent;
import com.laytonsmith.core.events.BindableEvent;
import com.laytonsmith.core.events.EventMixinInterface;
import com.laytonsmith.core.exceptions.EventException;
Expand All @@ -26,9 +26,9 @@

public class BukkitAbstractEventMixin implements EventMixinInterface {

AbstractEvent mySuper;
AbstractGenericEvent mySuper;

public BukkitAbstractEventMixin(AbstractEvent mySuper) {
public BukkitAbstractEventMixin(AbstractGenericEvent mySuper) {
this.mySuper = mySuper;
}

Expand Down
333 changes: 5 additions & 328 deletions src/main/java/com/laytonsmith/core/events/AbstractEvent.java
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> {

}
Loading

0 comments on commit 416a628

Please sign in to comment.