/*
* Copyright (c) 2015 NOVA, All rights reserved.
* This library is free software, licensed under GNU Lesser General Public License version 3
*
* This file is part of NOVA.
*
* NOVA is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NOVA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NOVA. If not, see <http://www.gnu.org/licenses/>.
*/
package nova.core.event.bus;
import nova.core.network.NetworkTarget;
import nova.core.network.NetworkTarget.Side;
import nova.core.network.Syncable;
import java.util.HashMap;
/**
* {@link EventBus} that can differentiate {@link NetworkTarget NetworkTargets}
* and allows registration of handlers that only listen on a specific
* {@link Side}. <b>Remember to {@link Side#reduce() reduce} the scope of any
* {@link SidedEvent} that was sent over the network!</b>
* @param <T> -Describe me-
* @author Vic Nightfall
*/
public class SidedEventBus<T extends CancelableEvent> extends CancelableEventBus<T> {
private NetworkEventProcessor eventProcessor;
private boolean checkListenedBeforeSend = true;
private HashMap<Class<?>, Side> listenedNetworkEvents = new HashMap<>();
public SidedEventBus(NetworkEventProcessor eventProcessor) {
this.eventProcessor = eventProcessor;
}
private void add(Class<?> clazz, Side side) {
if (side == Side.NONE) {
throw new IllegalArgumentException("Can't specify a sided event without a scope!");
}
Side side2 = listenedNetworkEvents.get(clazz);
if (side2 != null) {
if (side2 != Side.BOTH && side2 != side) {
listenedNetworkEvents.put(clazz, Side.BOTH);
}
} else {
listenedNetworkEvents.put(clazz, side);
}
}
private boolean contains(Class<?> clazz, Side side) {
Class<?> clazz2 = clazz;
while (true) {
Side side2 = listenedNetworkEvents.get(clazz2);
if (side2 == side || side2 == Side.BOTH) {
listenedNetworkEvents.put(clazz, side2);
return true;
}
if (clazz2 == Object.class) {
break;
}
clazz2 = clazz2.getSuperclass();
}
return false;
}
@Override
public void publish(T event) {
if (event instanceof SidedEventBus.SidedEvent) {
SidedEventBus.SidedEvent sidedEvent = (SidedEventBus.SidedEvent) event;
Side currentSide = Side.get();
if (sidedEvent.getTarget().opposite().targets(currentSide)) {
super.publish(event);
}
// Check if the event needs to be sent over the network.
if (currentSide.targets(sidedEvent.getTarget())) {
boolean send = !checkListenedBeforeSend;
if (!send) {
send = contains(event.getClass(), currentSide.opposite());
}
if (send) {
eventProcessor.handleEvent(sidedEvent);
}
}
} else {
super.publish(event);
}
}
@FunctionalInterface
public interface NetworkEventProcessor {
/**
* Gets called if the parent {@link SidedEventBus.SidedEventListener}
* received an event that needs to be sent over the network.
* @param event The event
*/
void handleEvent(SidedEvent event);
}
/**
* An event that specifies a {@link NetworkTarget}. Set the target by either
* overriding {@link #getTarget()} or by using the annotation
* {@link NetworkTarget} on the inherited class.
* @author Vic Nightfall
*/
public interface SidedEvent extends Syncable {
default Side getTarget() {
NetworkTarget target = getClass().getAnnotation(NetworkTarget.class);
return target != null ? target.value() : Side.BOTH;
}
}
protected static class SidedEventListener<E extends T, T> extends TypedEventListener<E, T> {
public final Side side;
public SidedEventListener(EventListener<E> wrappedListener, Class<E> eventClass, Side side) {
super(wrappedListener, eventClass);
this.side = side;
}
@Override
public void onEvent(T event) {
if (event instanceof SidedEventBus.SidedEvent) {
SidedEventBus.SidedEvent sidedEvent = (SidedEventBus.SidedEvent) event;
if (sidedEvent.getTarget().opposite().targets(side)) {
super.onEvent(event);
}
} else {
super.onEvent(event);
}
}
}
}