The client is offline due to RuneScape update. You may see instance errors but this is due to the update and not actual instance errors. This should be resolved soon. Join our Discord for more information.

Sleeping Until An Event



  • This is a method that will allow you to sleep until an event has occurred.

    /**
     * Because the way the RSPeer client handles events, you cannot use a generic EventListener,
     * but instead must use one of a specific type. To make this work, we can use JDK's Proxy
     * and InvocationHandler classes to make a new class object "implementing" an interface,
     * or interfaces. This is the resulting monstrosity of code that will make this work.
     * <p>
     * The method will sleep until the parameter Function<Event, Boolean> eventListener returns
     * true, or until the max duration is reached.
     *
     * @return True if the eventListener parameter returns as true within the given max duration,
     * otherwise, false in all other cases.
     */
    public static <T extends Event, L extends EventListener> boolean sleepUntilEvent(int maxDuration, @SuppressWarnings("unused") @NotNull Class<T> eventType, @NotNull Class<L> eventListenerType, @NotNull Function<T, Boolean> eventListener) {
        class ProxyEventListener implements InvocationHandler {
    
            private boolean isFired;
            private boolean result;
    
            private final Function<T, Boolean> eventListener;
    
            private ProxyEventListener(Function<T, Boolean> eventListener) {
                this.eventListener = eventListener;
            }
    
            @Override
            @SuppressWarnings("unchecked")
            public Object invoke(Object proxy, Method method, Object[] args) {
    
                String methodName = method.getName();
    
                switch (methodName) {
                    case "equals":
                        return equals$(proxy, args[0]);
    
                    case "hashCode":
                        return eventListener.hashCode();
    
                    case "notify":
                        if (isFired) {
                            return null;
                        }
    
                        Boolean listenerResult = eventListener.apply((T) args[0]);
                        if (listenerResult != null && listenerResult) {
                            isFired = true;
                            result = true;
                        }
    
                        return null;
    
                    default:
                        return null;
    
                }
            }
    
            private boolean equals$(Object me, Object other) {
                if (other == null || other.getClass() != me.getClass()) {
                    return false;
                }
    
                InvocationHandler handler = Proxy.getInvocationHandler(other);
                return handler instanceof ProxyEventListener && ((ProxyEventListener) handler).eventListener.equals(eventListener);
            }
        }
    
        if (eventListenerType == MouseInputListener.class || eventListenerType == KeyInputListener.class) {
            throw new IllegalArgumentException(String.format("Unsupported event type: %s", eventListenerType.getSimpleName()));
        }
    
        ProxyEventListener proxyEventListener = new ProxyEventListener(eventListener);
        EventListener listener = (EventListener) Proxy.newProxyInstance(eventListenerType.getClassLoader(), new Class[]{eventListenerType}, proxyEventListener);
    
        try {
            Game.getEventDispatcher().register(listener);
            return Time.sleepUntil(() -> proxyEventListener.result, 25, maxDuration);
        } finally {
            try {
                Game.getEventDispatcher().deregister(listener);
            } catch (Exception ignore) {
                // NOP
            }
        }
    }
    


  • Thanks, might come in handy!


  • Director

    Why would you ever need this lmao, the entire point of an event is to react to it, not poll until it happens. You are completely going against the entire idea of events.



  • @MadDev I can think of some usecases, imagine interacting with a sceneobject or npc and waiting for the next tick before continuing execution. With this you don't need to keep state in some variable that tells you if the event has happened yet since last time.


  • Director

    @TheHolyWaffle said in Sleeping Until An Event:

    @MadDev I can think of some usecases, imagine interacting with a sceneobject or npc and waiting for the next tick before continuing execution. With this you don't need to keep state in some variable that tells you if the event has happened yet since last time.

    I could see that, but I don't think registering and deregistering the event listener constantly is a good idea. I'm pretty sure the underlying collection is thread safe, so it'll end up just constantly copying the entire list over and over again to add/remove.

    There are better ways to do this.



  • Yes the list in EventDispatcher is a CopyOnWriteList impl and while I agree, there is an overhead, it should not be as noticeable as you make it out to be. As for the usefulness of the method, and whether or not it is the correct solution for a problem, is up to the implementer.


 

53
Online

16.6k
Users

1.4k
Topics

19.2k
Posts