Moved general purpose stuff into util, renamed utils->util
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
package mightypork.util.control;
|
||||
|
||||
|
||||
/**
|
||||
* Triggered action
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public abstract class Action implements Runnable, Enableable {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
|
||||
/**
|
||||
* Enable the action
|
||||
*
|
||||
* @param enable true to enable
|
||||
*/
|
||||
@Override
|
||||
public final void enable(boolean enable)
|
||||
{
|
||||
this.enabled = enable;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return true if this action is enabled.
|
||||
*/
|
||||
@Override
|
||||
public final boolean isEnabled()
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run the action, if it's enabled.
|
||||
*/
|
||||
@Override
|
||||
public final void run()
|
||||
{
|
||||
if (enabled) execute();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Do the work.
|
||||
*/
|
||||
protected abstract void execute();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package mightypork.util.control;
|
||||
|
||||
|
||||
/**
|
||||
* Element that can be assigned an action (ie. button);
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface ActionTrigger {
|
||||
|
||||
/**
|
||||
* Assign an action
|
||||
*
|
||||
* @param action action
|
||||
*/
|
||||
void setAction(Action action);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package mightypork.util.control;
|
||||
|
||||
|
||||
/**
|
||||
* Object that can be destroyed (free resources etc)
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface Destroyable {
|
||||
|
||||
/**
|
||||
* Destroy this object
|
||||
*/
|
||||
public void destroy();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package mightypork.util.control;
|
||||
|
||||
|
||||
/**
|
||||
* Can be enabled or disabled.<br>
|
||||
* Implementations should take appropriate action (ie. stop listening to events,
|
||||
* updating etc.)
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface Enableable {
|
||||
|
||||
/**
|
||||
* Change enabled state
|
||||
*
|
||||
* @param yes enabled
|
||||
*/
|
||||
public void enable(boolean yes);
|
||||
|
||||
|
||||
/**
|
||||
* @return true if enabled
|
||||
*/
|
||||
public boolean isEnabled();
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package mightypork.util.control.eventbus;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* HashSet that buffers add and remove calls and performs them all at once when
|
||||
* a flush() method is called.
|
||||
*
|
||||
* @author MightyPork
|
||||
* @param <E> element type
|
||||
*/
|
||||
public class BufferedHashSet<E> extends HashSet<E> {
|
||||
|
||||
private final List<E> toAdd = new LinkedList<>();
|
||||
private final List<Object> toRemove = new LinkedList<>();
|
||||
private boolean buffering = false;
|
||||
|
||||
|
||||
/**
|
||||
* make empty
|
||||
*/
|
||||
public BufferedHashSet() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* make from elements of a collection
|
||||
*
|
||||
* @param c
|
||||
*/
|
||||
public BufferedHashSet(Collection<? extends E> c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* make new
|
||||
*
|
||||
* @param initialCapacity
|
||||
* @param loadFactor
|
||||
*/
|
||||
public BufferedHashSet(int initialCapacity, float loadFactor) {
|
||||
super(initialCapacity, loadFactor);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* make new
|
||||
*
|
||||
* @param initialCapacity
|
||||
*/
|
||||
public BufferedHashSet(int initialCapacity) {
|
||||
super(initialCapacity);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean add(E e)
|
||||
{
|
||||
if (buffering) {
|
||||
toAdd.add(e);
|
||||
} else {
|
||||
super.add(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean remove(Object e)
|
||||
{
|
||||
if (buffering) {
|
||||
toRemove.add(e);
|
||||
} else {
|
||||
super.remove(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Flush all
|
||||
*/
|
||||
private void flush()
|
||||
{
|
||||
for (final E e : toAdd) {
|
||||
super.add(e);
|
||||
}
|
||||
|
||||
for (final Object e : toRemove) {
|
||||
super.remove(e);
|
||||
}
|
||||
|
||||
toAdd.clear();
|
||||
toRemove.clear();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Toggle buffering
|
||||
*
|
||||
* @param enable enable buffering
|
||||
*/
|
||||
public void setBuffering(boolean enable)
|
||||
{
|
||||
if (this.buffering && !enable) {
|
||||
flush();
|
||||
}
|
||||
|
||||
this.buffering = enable;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mightypork.util.control.eventbus;
|
||||
|
||||
|
||||
/**
|
||||
* Access to an {@link EventBus} instance
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface BusAccess {
|
||||
|
||||
/**
|
||||
* @return event bus
|
||||
*/
|
||||
EventBus getEventBus();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
package mightypork.util.control.eventbus;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.DelayQueue;
|
||||
import java.util.concurrent.Delayed;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import mightypork.util.annotations.FactoryMethod;
|
||||
import mightypork.util.control.Destroyable;
|
||||
import mightypork.util.control.eventbus.clients.DelegatingClient;
|
||||
import mightypork.util.control.eventbus.events.Event;
|
||||
import mightypork.util.control.eventbus.events.flags.DelayedEvent;
|
||||
import mightypork.util.control.eventbus.events.flags.ImmediateEvent;
|
||||
import mightypork.util.control.eventbus.events.flags.SingleReceiverEvent;
|
||||
import mightypork.util.control.eventbus.events.flags.UnloggedEvent;
|
||||
import mightypork.util.logging.Log;
|
||||
|
||||
|
||||
/**
|
||||
* An event bus, accommodating multiple {@link EventChannel}s.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
final public class EventBus implements Destroyable {
|
||||
|
||||
/** Message channels */
|
||||
private final BufferedHashSet<EventChannel<?, ?>> channels = new BufferedHashSet<>();
|
||||
|
||||
/** Registered clients */
|
||||
private final BufferedHashSet<Object> clients = new BufferedHashSet<>();
|
||||
|
||||
/** Messages queued for delivery */
|
||||
private final DelayQueue<DelayQueueEntry> sendQueue = new DelayQueue<>();
|
||||
|
||||
/** Queue polling thread */
|
||||
private final QueuePollingThread busThread;
|
||||
|
||||
/** Whether the bus was destroyed */
|
||||
private boolean dead = false;
|
||||
|
||||
/** Log detailed messages (debug) */
|
||||
public boolean detailedLogging = false;
|
||||
|
||||
|
||||
/**
|
||||
* Make a new bus and start it's queue thread.
|
||||
*/
|
||||
public EventBus() {
|
||||
busThread = new QueuePollingThread();
|
||||
busThread.setDaemon(true);
|
||||
busThread.start();
|
||||
}
|
||||
|
||||
|
||||
private boolean shallLog(Event<?> event)
|
||||
{
|
||||
if (!detailedLogging) return false;
|
||||
if (event.getClass().isAnnotationPresent(UnloggedEvent.class)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a {@link EventChannel} to this bus.<br>
|
||||
* If a channel of matching types is already added, it is returned instead.
|
||||
*
|
||||
* @param channel channel to be added
|
||||
* @return the channel that's now in the bus
|
||||
*/
|
||||
public EventChannel<?, ?> addChannel(EventChannel<?, ?> channel)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
// if the channel already exists, return this instance instead.
|
||||
for (final EventChannel<?, ?> ch : channels) {
|
||||
if (ch.equals(channel)) {
|
||||
Log.w("<bus> Channel of type " + Log.str(channel) + " already registered.");
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
|
||||
channels.add(channel);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make & connect a channel for given event and client type.
|
||||
*
|
||||
* @param eventClass event type
|
||||
* @param clientClass client type
|
||||
* @return the created channel instance
|
||||
*/
|
||||
@FactoryMethod
|
||||
public <F_EVENT extends Event<F_CLIENT>, F_CLIENT> EventChannel<?, ?> addChannel(Class<F_EVENT> eventClass, Class<F_CLIENT> clientClass)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
final EventChannel<F_EVENT, F_CLIENT> channel = EventChannel.create(eventClass, clientClass);
|
||||
return addChannel(channel);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a {@link EventChannel} from this bus
|
||||
*
|
||||
* @param channel true if channel was removed
|
||||
*/
|
||||
public void removeChannel(EventChannel<?, ?> channel)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
channels.remove(channel);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send based on annotation.clients
|
||||
*
|
||||
* @param event event
|
||||
*/
|
||||
public void send(Event<?> event)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
final DelayedEvent adelay = event.getClass().getAnnotation(DelayedEvent.class);
|
||||
if (adelay != null) {
|
||||
sendDelayed(event, adelay.delay());
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getClass().isAnnotationPresent(ImmediateEvent.class)) {
|
||||
sendDirect(event);
|
||||
return;
|
||||
}
|
||||
|
||||
sendQueued(event);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add event to a queue
|
||||
*
|
||||
* @param event event
|
||||
*/
|
||||
public void sendQueued(Event<?> event)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
sendDelayed(event, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add event to a queue, scheduled for given time.
|
||||
*
|
||||
* @param event event
|
||||
* @param delay delay before event is dispatched
|
||||
*/
|
||||
public void sendDelayed(Event<?> event, double delay)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
final DelayQueueEntry dm = new DelayQueueEntry(delay, event);
|
||||
|
||||
if (shallLog(event)) Log.f3("<bus> Qu " + Log.str(event) + ", t = +" + delay + "s");
|
||||
|
||||
sendQueue.add(dm);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send immediately.<br>
|
||||
* Should be used for real-time events that require immediate response, such
|
||||
* as timing events.
|
||||
*
|
||||
* @param event event
|
||||
*/
|
||||
public void sendDirect(Event<?> event)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
if (shallLog(event)) Log.f3("<bus> Di " + Log.str(event));
|
||||
|
||||
dispatch(event);
|
||||
}
|
||||
|
||||
|
||||
public void sendDirectToChildren(DelegatingClient delegatingClient, Event<?> event)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
if (shallLog(event)) Log.f3("<bus> Di sub " + Log.str(event));
|
||||
|
||||
doDispatch(delegatingClient.getChildClients(), event);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send immediately.<br>
|
||||
* Should be used for real-time events that require immediate response, such
|
||||
* as timing events.
|
||||
*
|
||||
* @param event event
|
||||
*/
|
||||
private void dispatch(Event<?> event)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
channels.setBuffering(true);
|
||||
clients.setBuffering(true);
|
||||
|
||||
doDispatch(clients, event);
|
||||
|
||||
channels.setBuffering(false);
|
||||
clients.setBuffering(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send to a set of clients
|
||||
*
|
||||
* @param clients clients
|
||||
* @param event event
|
||||
*/
|
||||
private void doDispatch(Collection<Object> clients, Event<?> event)
|
||||
{
|
||||
boolean sent = false;
|
||||
boolean accepted = false;
|
||||
|
||||
final boolean singular = event.getClass().isAnnotationPresent(SingleReceiverEvent.class);
|
||||
|
||||
for (final EventChannel<?, ?> b : channels) {
|
||||
if (b.canBroadcast(event)) {
|
||||
accepted = true;
|
||||
sent |= b.broadcast(event, clients);
|
||||
}
|
||||
|
||||
if (sent && singular) break;
|
||||
}
|
||||
|
||||
if (!accepted) Log.e("<bus> Not accepted by any channel: " + Log.str(event));
|
||||
if (!sent && shallLog(event)) Log.w("<bus> Not delivered: " + Log.str(event));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Connect a client to the bus. The client will be connected to all current
|
||||
* and future channels, until removed from the bus.
|
||||
*
|
||||
* @param client the client
|
||||
*/
|
||||
public void subscribe(Object client)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
if (client == null) return;
|
||||
|
||||
clients.add(client);
|
||||
|
||||
if (detailedLogging) Log.f3("<bus> Client joined: " + Log.str(client));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disconnect a client from the bus.
|
||||
*
|
||||
* @param client the client
|
||||
*/
|
||||
public void unsubscribe(Object client)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
clients.remove(client);
|
||||
|
||||
if (detailedLogging) Log.f3("<bus> Client left: " + Log.str(client));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if client can be accepted by any channel
|
||||
*
|
||||
* @param client tested client
|
||||
* @return would be accepted
|
||||
*/
|
||||
public boolean isClientValid(Object client)
|
||||
{
|
||||
assertLive();
|
||||
|
||||
if (client == null) return false;
|
||||
|
||||
for (final EventChannel<?, ?> ch : channels) {
|
||||
if (ch.isClientValid(client)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private class DelayQueueEntry implements Delayed {
|
||||
|
||||
private final long due;
|
||||
private Event<?> evt = null;
|
||||
|
||||
|
||||
public DelayQueueEntry(double seconds, Event<?> event) {
|
||||
super();
|
||||
this.due = System.currentTimeMillis() + (long) (seconds * 1000);
|
||||
this.evt = event;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int compareTo(Delayed o)
|
||||
{
|
||||
return Long.valueOf(getDelay(TimeUnit.MILLISECONDS)).compareTo(o.getDelay(TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getDelay(TimeUnit unit)
|
||||
{
|
||||
return unit.convert(due - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
|
||||
public Event<?> getEvent()
|
||||
{
|
||||
return evt;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class QueuePollingThread extends Thread {
|
||||
|
||||
public boolean stopped = false;
|
||||
|
||||
|
||||
public QueuePollingThread() {
|
||||
super("Queue Polling Thread");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
DelayQueueEntry evt;
|
||||
|
||||
while (!stopped) {
|
||||
evt = null;
|
||||
|
||||
try {
|
||||
evt = sendQueue.take();
|
||||
} catch (final InterruptedException ignored) {
|
||||
//
|
||||
}
|
||||
|
||||
if (evt != null) {
|
||||
dispatch(evt.getEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Halt bus thread and reject any future events.
|
||||
*/
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
assertLive();
|
||||
|
||||
busThread.stopped = true;
|
||||
|
||||
dead = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make sure the bus is not destroyed.
|
||||
*
|
||||
* @throws IllegalStateException if the bus is dead.
|
||||
*/
|
||||
private void assertLive() throws IllegalStateException
|
||||
{
|
||||
if (dead) throw new IllegalStateException("EventBus is dead.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package mightypork.util.control.eventbus;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
import mightypork.util.control.eventbus.clients.DelegatingClient;
|
||||
import mightypork.util.control.eventbus.clients.ToggleableClient;
|
||||
import mightypork.util.control.eventbus.events.Event;
|
||||
import mightypork.util.control.eventbus.events.flags.SingleReceiverEvent;
|
||||
import mightypork.util.logging.Log;
|
||||
|
||||
|
||||
/**
|
||||
* Event delivery channel, module of {@link EventBus}
|
||||
*
|
||||
* @author MightyPork
|
||||
* @param <EVENT> event type
|
||||
* @param <CLIENT> client (subscriber) type
|
||||
*/
|
||||
final public class EventChannel<EVENT extends Event<CLIENT>, CLIENT> {
|
||||
|
||||
private final Class<CLIENT> clientClass;
|
||||
private final Class<EVENT> eventClass;
|
||||
|
||||
|
||||
/**
|
||||
* Create a channel
|
||||
*
|
||||
* @param eventClass event class
|
||||
* @param clientClass client class
|
||||
*/
|
||||
public EventChannel(Class<EVENT> eventClass, Class<CLIENT> clientClass) {
|
||||
|
||||
if (eventClass == null || clientClass == null) {
|
||||
throw new NullPointerException("Null Event or Client class.");
|
||||
}
|
||||
|
||||
this.clientClass = clientClass;
|
||||
this.eventClass = eventClass;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to broadcast a event.<br>
|
||||
* If event is of wrong type, <code>false</code> is returned.
|
||||
*
|
||||
* @param event a event to be sent
|
||||
* @param clients collection of clients
|
||||
* @return true if event was sent
|
||||
*/
|
||||
public boolean broadcast(Event<?> event, Collection<Object> clients)
|
||||
{
|
||||
if (!canBroadcast(event)) return false;
|
||||
|
||||
return doBroadcast(eventClass.cast(event), clients, new HashSet<>());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send the event
|
||||
*
|
||||
* @param event sent event
|
||||
* @param clients subscribing clients
|
||||
* @param processed clients already processed
|
||||
* @return success
|
||||
*/
|
||||
private boolean doBroadcast(final EVENT event, final Collection<Object> clients, final Collection<Object> processed)
|
||||
{
|
||||
boolean sent = false;
|
||||
final boolean singular = event.getClass().isAnnotationPresent(SingleReceiverEvent.class);
|
||||
|
||||
for (final Object client : clients) {
|
||||
|
||||
// exclude obvious non-clients
|
||||
if (!isClientValid(client)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// avoid executing more times
|
||||
if (processed.contains(client)) {
|
||||
Log.w("<bus> Client already served: " + Log.str(client));
|
||||
continue;
|
||||
}
|
||||
processed.add(client);
|
||||
|
||||
// opt-out
|
||||
if (client instanceof ToggleableClient) {
|
||||
if (!((ToggleableClient) client).isListening()) continue;
|
||||
}
|
||||
|
||||
sent |= sendTo(client, event);
|
||||
|
||||
// singular event ain't no whore, handled once only.
|
||||
if (sent && singular) return true;
|
||||
|
||||
// pass on to delegated clients
|
||||
if (client instanceof DelegatingClient) {
|
||||
if (((DelegatingClient) client).doesDelegate()) {
|
||||
|
||||
final Collection<Object> children = ((DelegatingClient) client).getChildClients();
|
||||
|
||||
if (children != null && children.size() > 0) {
|
||||
sent |= doBroadcast(event, children, processed);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sent;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send an event to a client.
|
||||
*
|
||||
* @param client target client
|
||||
* @param event event to send
|
||||
* @return success
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean sendTo(Object client, EVENT event)
|
||||
{
|
||||
if (isClientOfType(client)) {
|
||||
((Event<CLIENT>) event).handleBy((CLIENT) client);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the given event can be broadcasted by this {@link EventChannel}
|
||||
*
|
||||
* @param event event object
|
||||
* @return can be broadcasted
|
||||
*/
|
||||
public boolean canBroadcast(Event<?> event)
|
||||
{
|
||||
return event != null && eventClass.isInstance(event);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance for given types
|
||||
*
|
||||
* @param eventClass event class
|
||||
* @param clientClass client class
|
||||
* @return the broadcaster
|
||||
*/
|
||||
public static <F_EVENT extends Event<F_CLIENT>, F_CLIENT> EventChannel<F_EVENT, F_CLIENT> create(Class<F_EVENT> eventClass, Class<F_CLIENT> clientClass)
|
||||
{
|
||||
return new EventChannel<>(eventClass, clientClass);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if client is of channel type
|
||||
*
|
||||
* @param client client
|
||||
* @return is of type
|
||||
*/
|
||||
private boolean isClientOfType(Object client)
|
||||
{
|
||||
return clientClass.isInstance(client);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the channel is compatible with given
|
||||
*
|
||||
* @param client client
|
||||
* @return is supported
|
||||
*/
|
||||
public boolean isClientValid(Object client)
|
||||
{
|
||||
return isClientOfType(client) || (client instanceof DelegatingClient);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
final int prime = 13;
|
||||
int result = 1;
|
||||
result = prime * result + ((clientClass == null) ? 0 : clientClass.hashCode());
|
||||
result = prime * result + ((eventClass == null) ? 0 : eventClass.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (!(obj instanceof EventChannel)) return false;
|
||||
final EventChannel<?, ?> other = (EventChannel<?, ?>) obj;
|
||||
if (clientClass == null) {
|
||||
if (other.clientClass != null) return false;
|
||||
} else if (!clientClass.equals(other.clientClass)) return false;
|
||||
if (eventClass == null) {
|
||||
if (other.eventClass != null) return false;
|
||||
} else if (!eventClass.equals(other.eventClass)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "{ " + Log.str(eventClass) + " => " + Log.str(clientClass) + " }";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package mightypork.util.control.eventbus.clients;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import mightypork.util.control.eventbus.BusAccess;
|
||||
import mightypork.util.control.eventbus.EventBus;
|
||||
|
||||
|
||||
/**
|
||||
* Client that can be attached to the {@link EventBus}, or added as a child
|
||||
* client to another {@link DelegatingClient}
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public abstract class BusNode implements BusAccess, ClientHub {
|
||||
|
||||
private final BusAccess busAccess;
|
||||
|
||||
private final Set<Object> clients = new LinkedHashSet<>();
|
||||
private boolean listening = true;
|
||||
private boolean delegating = true;
|
||||
|
||||
|
||||
/**
|
||||
* @param busAccess access to bus
|
||||
*/
|
||||
public BusNode(BusAccess busAccess) {
|
||||
this.busAccess = busAccess;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final Collection<Object> getChildClients()
|
||||
{
|
||||
return clients;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final boolean doesDelegate()
|
||||
{
|
||||
return delegating;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final boolean isListening()
|
||||
{
|
||||
return listening;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a child subscriber to the {@link EventBus}.<br>
|
||||
*
|
||||
* @param client
|
||||
*/
|
||||
@Override
|
||||
public final void addChildClient(Object client)
|
||||
{
|
||||
if (client instanceof RootBusNode) {
|
||||
throw new IllegalArgumentException("Cannot nest RootBusNode.");
|
||||
}
|
||||
|
||||
if (getEventBus().isClientValid(client)) {
|
||||
clients.add(client);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a child subscriber
|
||||
*
|
||||
* @param client subscriber to remove
|
||||
*/
|
||||
@Override
|
||||
public final void removeChildClient(Object client)
|
||||
{
|
||||
if (client != null) {
|
||||
clients.remove(client);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether events should be received.
|
||||
*
|
||||
* @param listening receive events
|
||||
*/
|
||||
public final void setListening(boolean listening)
|
||||
{
|
||||
this.listening = listening;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether events should be passed on to child nodes
|
||||
*
|
||||
* @param delegating
|
||||
*/
|
||||
public final void setDelegating(boolean delegating)
|
||||
{
|
||||
this.delegating = delegating;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final EventBus getEventBus()
|
||||
{
|
||||
return busAccess.getEventBus();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package mightypork.util.control.eventbus.clients;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import mightypork.util.control.eventbus.EventBus;
|
||||
|
||||
|
||||
/**
|
||||
* Common methods for client hubs (ie delegating vlient implementations)
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface ClientHub extends DelegatingClient, ToggleableClient {
|
||||
|
||||
@Override
|
||||
public boolean doesDelegate();
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<Object> getChildClients();
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isListening();
|
||||
|
||||
|
||||
/**
|
||||
* Add a child subscriber to the {@link EventBus}.<br>
|
||||
*
|
||||
* @param client
|
||||
*/
|
||||
public void addChildClient(Object client);
|
||||
|
||||
|
||||
/**
|
||||
* Remove a child subscriber
|
||||
*
|
||||
* @param client subscriber to remove
|
||||
*/
|
||||
void removeChildClient(Object client);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package mightypork.util.control.eventbus.clients;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
|
||||
/**
|
||||
* Client containing child clients. According to the contract, if the collection
|
||||
* of clients is ordered, the clients will be served in that order. In any case,
|
||||
* the {@link DelegatingClient} itself will be served beforehand.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface DelegatingClient {
|
||||
|
||||
/**
|
||||
* @return collection of child clients. Can not be null.
|
||||
*/
|
||||
public Collection<Object> getChildClients();
|
||||
|
||||
|
||||
/**
|
||||
* @return true if delegating is active
|
||||
*/
|
||||
public boolean doesDelegate();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package mightypork.util.control.eventbus.clients;
|
||||
|
||||
|
||||
import mightypork.util.control.Destroyable;
|
||||
import mightypork.util.control.eventbus.BusAccess;
|
||||
|
||||
|
||||
/**
|
||||
* Bus node that should be directly attached to the bus.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public abstract class RootBusNode extends BusNode implements Destroyable {
|
||||
|
||||
/**
|
||||
* @param busAccess access to bus
|
||||
*/
|
||||
public RootBusNode(BusAccess busAccess) {
|
||||
super(busAccess);
|
||||
|
||||
getEventBus().subscribe(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void destroy()
|
||||
{
|
||||
deinit();
|
||||
|
||||
getEventBus().unsubscribe(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deinitialize the subsystem<br>
|
||||
* (called during destruction)
|
||||
*/
|
||||
protected abstract void deinit();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mightypork.util.control.eventbus.clients;
|
||||
|
||||
|
||||
/**
|
||||
* Client that can toggle receiving messages.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface ToggleableClient {
|
||||
|
||||
/**
|
||||
* @return true if the client wants to receive messages
|
||||
*/
|
||||
public boolean isListening();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package mightypork.util.control.eventbus.events;
|
||||
|
||||
|
||||
import mightypork.util.control.eventbus.events.flags.DelayedEvent;
|
||||
import mightypork.util.control.eventbus.events.flags.ImmediateEvent;
|
||||
import mightypork.util.control.eventbus.events.flags.SingleReceiverEvent;
|
||||
import mightypork.util.control.eventbus.events.flags.UnloggedEvent;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Something that can be handled by HANDLER.
|
||||
* </p>
|
||||
* <p>
|
||||
* Can be annotated as {@link SingleReceiverEvent} to be delivered once only,
|
||||
* and {@link DelayedEvent} or {@link ImmediateEvent} to specify default sending
|
||||
* mode. When marked as {@link UnloggedEvent}, it will not appear in detailed
|
||||
* bus logging (useful for very frequent events, such as UpdateEvent).
|
||||
* </p>
|
||||
* <p>
|
||||
* Default sending mode (if not changed by annotations) is <i>queued</i> with
|
||||
* zero delay.
|
||||
* </p>
|
||||
*
|
||||
* @author MightyPork
|
||||
* @param <HANDLER> handler type
|
||||
*/
|
||||
public interface Event<HANDLER> {
|
||||
|
||||
/**
|
||||
* Ask handler to handle this message.
|
||||
*
|
||||
* @param handler handler instance
|
||||
*/
|
||||
public void handleBy(HANDLER handler);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package mightypork.util.control.eventbus.events.flags;
|
||||
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
|
||||
/**
|
||||
* Event that should be queued with given delay (default: 0);
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface DelayedEvent {
|
||||
|
||||
/**
|
||||
* @return event dispatch delay [seconds]
|
||||
*/
|
||||
double delay() default 0;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mightypork.util.control.eventbus.events.flags;
|
||||
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
|
||||
/**
|
||||
* Event that should not be queued.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface ImmediateEvent {}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mightypork.util.control.eventbus.events.flags;
|
||||
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
|
||||
/**
|
||||
* Handled only by the first client, then discarded.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface SingleReceiverEvent {}
|
||||
@@ -0,0 +1,17 @@
|
||||
package mightypork.util.control.eventbus.events.flags;
|
||||
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
|
||||
/**
|
||||
* Event that's not worth logging, unless there was an error with it.<br>
|
||||
* Useful for common events that would otherwise clutter the log.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface UnloggedEvent {}
|
||||
@@ -0,0 +1,39 @@
|
||||
package mightypork.util.control.timing;
|
||||
|
||||
|
||||
/**
|
||||
* Class for counting FPS in games.<br>
|
||||
* This class can be used also as a simple frequency meter - output is in Hz.
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class FpsMeter {
|
||||
|
||||
private long frames = 0;
|
||||
private long lastTimeMillis = System.currentTimeMillis();
|
||||
private long lastSecFPS = 0;
|
||||
|
||||
|
||||
/**
|
||||
* @return current second's FPS
|
||||
*/
|
||||
public long getFPS()
|
||||
{
|
||||
return lastSecFPS;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notification that frame was rendered
|
||||
*/
|
||||
public void frame()
|
||||
{
|
||||
if (System.currentTimeMillis() - lastTimeMillis > 1000) {
|
||||
lastSecFPS = frames;
|
||||
frames = 0;
|
||||
final long over = System.currentTimeMillis() - lastTimeMillis - 1000;
|
||||
lastTimeMillis = System.currentTimeMillis() - over;
|
||||
}
|
||||
frames++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package mightypork.util.control.timing;
|
||||
|
||||
|
||||
/**
|
||||
* Can be paused & resumed
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface Pauseable {
|
||||
|
||||
/**
|
||||
* Pause operation
|
||||
*/
|
||||
public void pause();
|
||||
|
||||
|
||||
/**
|
||||
* Resume operation
|
||||
*/
|
||||
public void resume();
|
||||
|
||||
|
||||
/**
|
||||
* @return paused state
|
||||
*/
|
||||
public boolean isPaused();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package mightypork.util.control.timing;
|
||||
|
||||
|
||||
/**
|
||||
* Timer for delta timing
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class TimerDelta {
|
||||
|
||||
private long lastFrame;
|
||||
|
||||
private static final long SECOND = 1000000000; // a million nanoseconds
|
||||
|
||||
|
||||
/**
|
||||
* New delta timer
|
||||
*/
|
||||
public TimerDelta() {
|
||||
lastFrame = System.nanoTime();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get current time in NS
|
||||
*
|
||||
* @return current time NS
|
||||
*/
|
||||
public long getTime()
|
||||
{
|
||||
return System.nanoTime();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get time since the last "getDelta()" call.
|
||||
*
|
||||
* @return delta time (seconds)
|
||||
*/
|
||||
public double getDelta()
|
||||
{
|
||||
final long time = getTime();
|
||||
final double delta = (time - lastFrame) / (double) SECOND;
|
||||
lastFrame = time;
|
||||
return delta;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package mightypork.util.control.timing;
|
||||
|
||||
|
||||
/**
|
||||
* Timer for interpolated timing
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public class TimerFps {
|
||||
|
||||
private long lastFrame = 0;
|
||||
private long nextFrame = 0;
|
||||
private long skipped = 0;
|
||||
private long lastSkipped = 0;
|
||||
|
||||
private static final long SECOND = 1000000000; // a million nanoseconds
|
||||
private final long FRAME; // a time of one frame in nanoseconds
|
||||
|
||||
|
||||
/**
|
||||
* New interpolated timer
|
||||
*
|
||||
* @param fps target FPS
|
||||
*/
|
||||
public TimerFps(long fps) {
|
||||
FRAME = Math.round(SECOND / (double) fps);
|
||||
|
||||
lastFrame = System.nanoTime();
|
||||
nextFrame = System.nanoTime() + FRAME;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sync and calculate dropped frames etc.
|
||||
*/
|
||||
public void sync()
|
||||
{
|
||||
final long time = getTime();
|
||||
if (time >= nextFrame) {
|
||||
final long skippedNow = (long) Math.floor((time - nextFrame) / (double) FRAME) + 1;
|
||||
skipped += skippedNow;
|
||||
lastFrame = nextFrame + (1 - skippedNow) * FRAME;
|
||||
nextFrame += skippedNow * FRAME;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get nanotime
|
||||
*
|
||||
* @return nanotime
|
||||
*/
|
||||
public long getTime()
|
||||
{
|
||||
return System.nanoTime();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get fraction of next frame
|
||||
*
|
||||
* @return fraction
|
||||
*/
|
||||
public double getFraction()
|
||||
{
|
||||
if (getSkipped() >= 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
final long time = getTime();
|
||||
|
||||
if (time <= nextFrame) {
|
||||
return (double) (time - lastFrame) / (double) FRAME;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get number of elapsed ticks
|
||||
*
|
||||
* @return ticks
|
||||
*/
|
||||
public int getSkipped()
|
||||
{
|
||||
final long change = skipped - lastSkipped;
|
||||
lastSkipped = skipped;
|
||||
return (int) change;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear timer and start counting new tick.
|
||||
*/
|
||||
public void startNewFrame()
|
||||
{
|
||||
final long time = getTime();
|
||||
lastFrame = time;
|
||||
nextFrame = time + FRAME;
|
||||
lastSkipped = skipped;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package mightypork.util.control.timing;
|
||||
|
||||
|
||||
/**
|
||||
* Uses delta timing
|
||||
*
|
||||
* @author MightyPork
|
||||
*/
|
||||
public interface Updateable {
|
||||
|
||||
/**
|
||||
* Update item state based on elapsed time
|
||||
*
|
||||
* @param delta time elapsed since last update, in seconds
|
||||
*/
|
||||
public void update(double delta);
|
||||
}
|
||||
Reference in New Issue
Block a user