From 4a1a84fd876aa62142171eb098c27042550da2d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Tue, 1 Apr 2014 00:55:43 +0200 Subject: [PATCH] Refactoring of subsystems and eventbus --- .gitignore | 3 +- README.md | 20 +- res/.gitkeep | 0 src/mightypork/rogue/App.java | 30 +-- src/mightypork/rogue/AppAccess.java | 11 +- src/mightypork/rogue/AppAdapter.java | 61 +++++ src/mightypork/rogue/AppSubsystem.java | 209 ----------------- .../rogue/bus/DelegatingBusClient.java | 162 +++++++++++++ src/mightypork/rogue/bus/SimpleBusClient.java | 23 ++ src/mightypork/rogue/bus/UpdateReceiver.java | 26 +++ .../{input => bus}/events/KeyboardEvent.java | 2 +- .../events/MouseButtonEvent.java | 2 +- .../events/MouseMotionEvent.java | 2 +- .../events/ScreenChangeEvent.java | 2 +- .../{display => bus}/events/UpdateEvent.java | 2 +- .../rogue/display/DisplaySystem.java | 14 +- src/mightypork/rogue/display/Screen.java | 13 +- .../rogue/display/ScreenTestAnimations.java | 2 +- .../{ConstraintContext.java => Bounding.java} | 4 +- .../rogue/display/constraints/Constraint.java | 29 ++- .../rogue/display/rendering/Renderable.java | 8 + .../rogue/display/rendering/ScreenLayer.java | 18 ++ src/mightypork/rogue/input/InputSystem.java | 52 ++--- src/mightypork/rogue/input/KeyBinding.java | 2 +- .../rogue/input/KeyBindingPool.java | 2 +- src/mightypork/rogue/sounds/SoundSystem.java | 4 +- .../rogue/testing/TestConstraints.java | 8 +- src/mightypork/rogue/testing/TestMsgbus.java | 219 ++++++++++++++++++ src/mightypork/rogue/textures/Textures.java | 105 +-------- src/mightypork/rogue/textures/Tx.java | 2 + src/mightypork/utils/logging/Log.java | 20 ++ src/mightypork/utils/logging/LogInstance.java | 11 +- .../utils/objects/VarargsParser.java | 3 + .../patterns/subscription/MessageBus.java | 46 ++-- .../patterns/subscription/MessageChannel.java | 119 +++++----- .../patterns/subscription/Subscribable.java | 26 --- .../clients/DelegatingClient.java | 25 ++ .../clients/ToggleableClient.java | 16 ++ 38 files changed, 796 insertions(+), 507 deletions(-) create mode 100644 res/.gitkeep create mode 100644 src/mightypork/rogue/AppAdapter.java delete mode 100644 src/mightypork/rogue/AppSubsystem.java create mode 100644 src/mightypork/rogue/bus/DelegatingBusClient.java create mode 100644 src/mightypork/rogue/bus/SimpleBusClient.java create mode 100644 src/mightypork/rogue/bus/UpdateReceiver.java rename src/mightypork/rogue/{input => bus}/events/KeyboardEvent.java (96%) rename src/mightypork/rogue/{input => bus}/events/MouseButtonEvent.java (97%) rename src/mightypork/rogue/{input => bus}/events/MouseMotionEvent.java (95%) rename src/mightypork/rogue/{display => bus}/events/ScreenChangeEvent.java (95%) rename src/mightypork/rogue/{display => bus}/events/UpdateEvent.java (91%) rename src/mightypork/rogue/display/constraints/{ConstraintContext.java => Bounding.java} (69%) create mode 100644 src/mightypork/rogue/display/rendering/Renderable.java create mode 100644 src/mightypork/rogue/display/rendering/ScreenLayer.java create mode 100644 src/mightypork/rogue/testing/TestMsgbus.java delete mode 100644 src/mightypork/utils/patterns/subscription/Subscribable.java create mode 100644 src/mightypork/utils/patterns/subscription/clients/DelegatingClient.java create mode 100644 src/mightypork/utils/patterns/subscription/clients/ToggleableClient.java diff --git a/.gitignore b/.gitignore index 92145bc..97adb72 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /bin/ -/target/ \ No newline at end of file +/target/ +*.log \ No newline at end of file diff --git a/README.md b/README.md index 7f5350a..d736bc7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -Dungeon crawler RPG -=================== +Rogue - dungeon crawler +======================= Goals ----- @@ -11,10 +11,19 @@ Goals Features -------- -- Randomly generated floors -- Hybrid turn-based gameplay +- Full OOP design +- Event driven +- OpenGL 2D rendering + +- Random floors +- Real-time gameplay +- Monsters with AI (-> combat system) + + +Possibly added +-------------- + - Stats and leveling -- Monsters - Collectable items - Potions, food - Simple inventory system @@ -24,5 +33,4 @@ Used libraries -------------- - Slick2D -- NiftyGUI - LWJGL \ No newline at end of file diff --git a/res/.gitkeep b/res/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/mightypork/rogue/App.java b/src/mightypork/rogue/App.java index 4a1c8df..dcf4ea1 100644 --- a/src/mightypork/rogue/App.java +++ b/src/mightypork/rogue/App.java @@ -7,10 +7,10 @@ import java.nio.channels.FileLock; import javax.swing.JOptionPane; +import mightypork.rogue.bus.events.UpdateEvent; import mightypork.rogue.display.DisplaySystem; import mightypork.rogue.display.Screen; import mightypork.rogue.display.ScreenTestAnimations; -import mightypork.rogue.display.events.UpdateEvent; import mightypork.rogue.input.InputSystem; import mightypork.rogue.input.KeyStroke; import mightypork.rogue.sounds.SoundSystem; @@ -25,6 +25,11 @@ import mightypork.utils.time.TimerDelta; import org.lwjgl.input.Keyboard; +/** + * Main class + * + * @author MightyPork + */ public class App implements Destroyable, AppAccess { /** instance pointer */ @@ -61,7 +66,7 @@ public class App implements Destroyable, AppAccess { /** - * Show crash report dialog with error stack trace. + * Handle a crash * * @param error */ @@ -69,15 +74,12 @@ public class App implements Destroyable, AppAccess { { Log.e("The game has crashed.", error); - if (inst != null) inst.exit(); + if (inst != null) inst.shutdown(); } - /** - * Quit to OS
- * Destroy app & exit VM - */ - public void exit() + @Override + public void shutdown() { destroy(); System.exit(0); @@ -121,7 +123,7 @@ public class App implements Destroyable, AppAccess { ); //@formatter:on - exit(); + shutdown(); return; } } @@ -165,7 +167,7 @@ public class App implements Destroyable, AppAccess { private void initBus() { events = new MessageBus(); - events.addSubscriber(this); + events.subscribe(this); events.createChannel(UpdateEvent.class, UpdateEvent.Listener.class); } @@ -214,7 +216,7 @@ public class App implements Destroyable, AppAccess { public void run() { Log.f3("CTRL+Q, shutting down."); - exit(); + shutdown(); } }); } @@ -246,7 +248,7 @@ public class App implements Destroyable, AppAccess { { initialize(); mainLoop(); - exit(); + shutdown(); } /** timer */ @@ -293,7 +295,7 @@ public class App implements Destroyable, AppAccess { * @return sound system of the running instance */ @Override - public SoundSystem soundsys() + public SoundSystem snd() { return sounds; } @@ -323,7 +325,7 @@ public class App implements Destroyable, AppAccess { * @return event bus */ @Override - public MessageBus msgbus() + public MessageBus bus() { return events; } diff --git a/src/mightypork/rogue/AppAccess.java b/src/mightypork/rogue/AppAccess.java index c2776d2..6a7796a 100644 --- a/src/mightypork/rogue/AppAccess.java +++ b/src/mightypork/rogue/AppAccess.java @@ -17,7 +17,7 @@ public interface AppAccess { /** * @return sound system */ - abstract SoundSystem soundsys(); + abstract SoundSystem snd(); /** @@ -35,6 +35,13 @@ public interface AppAccess { /** * @return event bus */ - abstract MessageBus msgbus(); + abstract MessageBus bus(); + + + /** + * Quit to OS
+ * Destroy app & exit VM + */ + abstract void shutdown(); } diff --git a/src/mightypork/rogue/AppAdapter.java b/src/mightypork/rogue/AppAdapter.java new file mode 100644 index 0000000..19c847d --- /dev/null +++ b/src/mightypork/rogue/AppAdapter.java @@ -0,0 +1,61 @@ +package mightypork.rogue; + + +import mightypork.rogue.display.DisplaySystem; +import mightypork.rogue.input.InputSystem; +import mightypork.rogue.sounds.SoundSystem; +import mightypork.utils.patterns.subscription.MessageBus; + + +/** + * App access adapter + * + * @author MightyPork + */ +public class AppAdapter implements AppAccess { + + private AppAccess app; + + + public AppAdapter(AppAccess app) { + if (app == null) throw new NullPointerException("AppAccess instance cannot be null."); + + this.app = app; + } + + + @Override + public final SoundSystem snd() + { + return app.snd(); + } + + + @Override + public final InputSystem input() + { + return app.input(); + } + + + @Override + public final DisplaySystem disp() + { + return app.disp(); + } + + + @Override + public final MessageBus bus() + { + return app.bus(); + } + + + @Override + public void shutdown() + { + app.shutdown(); + } + +} diff --git a/src/mightypork/rogue/AppSubsystem.java b/src/mightypork/rogue/AppSubsystem.java deleted file mode 100644 index 0625cd1..0000000 --- a/src/mightypork/rogue/AppSubsystem.java +++ /dev/null @@ -1,209 +0,0 @@ -package mightypork.rogue; - - -import java.util.HashSet; -import java.util.Set; - -import mightypork.rogue.display.DisplaySystem; -import mightypork.rogue.display.events.UpdateEvent; -import mightypork.rogue.input.InputSystem; -import mightypork.rogue.sounds.SoundSystem; -import mightypork.utils.logging.Log; -import mightypork.utils.patterns.Destroyable; -import mightypork.utils.patterns.subscription.MessageBus; -import mightypork.utils.time.Updateable; - - -public abstract class AppSubsystem implements AppAccess, UpdateEvent.Listener, Updateable, Destroyable { - - private AppAccess app; - private boolean wantUpdates; - private boolean destroyed = false; - - /** Subsystem children subscribing to MessageBus */ - private Set childSubscribers = new HashSet(); - - - /** - * Create a subsystem - * - * @param app app instance access - * @param joinBus whether to initially join msgbus - */ - public AppSubsystem(AppAccess app, boolean joinBus) { - this.app = app; - - // add to subscriber group - childSubscribers.add(this); - - enableEvents(joinBus); - - enableUpdates(true); - - init(); - } - - - /** - * Set whether events should be received.
- * This includes {@link UpdateEvent}, so disabling events also disables - * updates. - * - * @param enable - */ - protected final void enableEvents(boolean enable) - { - assertLive(); - - // this & child subscribers - for (Object o : childSubscribers) { - if (enable) { - app.msgbus().addSubscriber(o); - } else { - app.msgbus().removeSubscriber(o); - } - } - } - - - /** - * Set whether to receive {@link UpdateEvent}s (delta timing, one each - * frame).
- * - * @param enable - */ - protected final void enableUpdates(boolean enable) - { - assertLive(); - - wantUpdates = enable; - } - - - @Override - public final void receive(UpdateEvent event) - { - assertLive(); - - if (wantUpdates) update(event.getDeltaTime()); - } - - -// /** -// * @return app instance -// */ -// protected AppAccess app() -// { -// assertLive(); -// -// return app; -// } - - @Override - public void update(double delta) - { - Log.w("Subsystem " + getClass().getSimpleName() + " receives updates, but does not override the update() method."); - } - - - /** - * Initialize the subsystem
- * (called during construction) - */ - protected abstract void init(); - - - /** - * Deinitialize the subsystem
- * (called during destruction)
- *
- * All child eventbus subscribers will be removed from the eventbus. - */ - protected abstract void deinit(); - - - /** - * Add a child subscriber to the {@link MessageBus}.
- * Child subscribers are removed when subsystem is destroyed, and can be - * connected/disconnected using the enableEvents() method. - * - * @param client - * @return true on success - */ - public final boolean addChildSubscriber(Object client) - { - assertLive(); - - if (client == null) return false; - - msgbus().addSubscriber(client); - childSubscribers.add(client); - - return true; - } - - - public final void removeChildSubscriber(Object client) - { - assertLive(); - - childSubscribers.remove(client); - msgbus().removeSubscriber(client); - } - - - @Override - public final void destroy() - { - assertLive(); - - deinit(); - - enableEvents(false); // remove all subscribers from bus - app = null; - - destroyed = true; - } - - - @Override - public MessageBus msgbus() - { - assertLive(); - - return app.msgbus(); - } - - - @Override - public SoundSystem soundsys() - { - assertLive(); - - return app.soundsys(); - } - - - @Override - public InputSystem input() - { - assertLive(); - - return app.input(); - } - - - @Override - public DisplaySystem disp() - { - assertLive(); - - return app.disp(); - } - - - private void assertLive() - { - if (destroyed) throw new IllegalStateException("Subsystem already destroyed."); - } -} diff --git a/src/mightypork/rogue/bus/DelegatingBusClient.java b/src/mightypork/rogue/bus/DelegatingBusClient.java new file mode 100644 index 0000000..a322e90 --- /dev/null +++ b/src/mightypork/rogue/bus/DelegatingBusClient.java @@ -0,0 +1,162 @@ +package mightypork.rogue.bus; + + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import mightypork.rogue.AppAccess; +import mightypork.rogue.AppAdapter; +import mightypork.rogue.bus.events.UpdateEvent; +import mightypork.utils.logging.Log; +import mightypork.utils.patterns.Destroyable; +import mightypork.utils.patterns.subscription.MessageBus; +import mightypork.utils.patterns.subscription.clients.DelegatingClient; +import mightypork.utils.patterns.subscription.clients.ToggleableClient; +import mightypork.utils.time.Updateable; + + +/** + * App event bus client, to be used for subsystems, screens and anything that + * needs access to the eventbus + * + * @author MightyPork + */ +public abstract class DelegatingBusClient extends AppAdapter implements DelegatingClient, ToggleableClient, Destroyable, Updateable, UpdateEvent.Listener { + + /** Subsystem children subscribing to MessageBus */ + private Set childSubscribers = new HashSet(); + + private boolean wantUpdates = true; + private boolean eventsEnabled = true; + + + public DelegatingBusClient(AppAccess app, boolean updates) { + super(app); + + bus().subscribe(this); + + enableUpdates(updates); + + init(); + } + + + /** + * Add a child subscriber to the {@link MessageBus}.
+ * + * @param client + * @return true on success + */ + public final boolean addChildSubscriber(Object client) + { + if (client == null) return false; + + childSubscribers.add(client); + + return true; + } + + + /** + * Remove a child subscriber + * + * @param client subscriber to remove + */ + public final void removeChildSubscriber(Object client) + { + if (client == null) return; + + childSubscribers.remove(client); + } + + + @Override + public final Collection getChildClients() + { + return childSubscribers; + } + + + @Override + public final boolean doesDelegate() + { + return doesSubscribe(); + } + + + /** + * Set whether to receive {@link UpdateEvent}s (delta timing, one each + * frame).
+ * + * @param enable + */ + public final void enableUpdates(boolean enable) + { + wantUpdates = enable; + } + + + @Override + public final boolean doesSubscribe() + { + return eventsEnabled; + } + + + /** + * Set whether events should be received. + * + * @param enable + */ + public final void enableEvents(boolean enable) + { + this.eventsEnabled = enable; + } + + + @Override + public final void receive(UpdateEvent event) + { + if (wantUpdates) update(event.getDeltaTime()); + } + + + @Override + public final void destroy() + { + deinit(); + + enableUpdates(false); + + bus().unsubscribe(this); + } + + + @Override + public void update(double delta) + { + Log.w("Client " + getClass().getSimpleName() + " receives updates, but does not override the update() method."); + } + + + /** + * Initialize the subsystem
+ * (called during construction) + */ + protected void init() + { + // no impl + } + + + /** + * Deinitialize the subsystem
+ * (called during destruction) + */ + protected void deinit() + { + // no impl + } + +} diff --git a/src/mightypork/rogue/bus/SimpleBusClient.java b/src/mightypork/rogue/bus/SimpleBusClient.java new file mode 100644 index 0000000..f5c40db --- /dev/null +++ b/src/mightypork/rogue/bus/SimpleBusClient.java @@ -0,0 +1,23 @@ +package mightypork.rogue.bus; + + +import mightypork.rogue.AppAccess; +import mightypork.rogue.AppAdapter; + + +/** + * Simplest event bus client with app access + * + * @author MightyPork + */ +public class SimpleBusClient extends AppAdapter { + + /** + * @param app app access + */ + public SimpleBusClient(AppAccess app) { + super(app); + + bus().subscribe(this); + } +} diff --git a/src/mightypork/rogue/bus/UpdateReceiver.java b/src/mightypork/rogue/bus/UpdateReceiver.java new file mode 100644 index 0000000..db525a8 --- /dev/null +++ b/src/mightypork/rogue/bus/UpdateReceiver.java @@ -0,0 +1,26 @@ +package mightypork.rogue.bus; + + +import mightypork.rogue.AppAccess; +import mightypork.rogue.bus.events.UpdateEvent; +import mightypork.utils.time.Updateable; + + +public abstract class UpdateReceiver extends SimpleBusClient implements UpdateEvent.Listener, Updateable { + + public UpdateReceiver(AppAccess app) { + super(app); + } + + + @Override + public void receive(UpdateEvent event) + { + update(event.getDeltaTime()); + } + + + @Override + public abstract void update(double delta); + +} diff --git a/src/mightypork/rogue/input/events/KeyboardEvent.java b/src/mightypork/rogue/bus/events/KeyboardEvent.java similarity index 96% rename from src/mightypork/rogue/input/events/KeyboardEvent.java rename to src/mightypork/rogue/bus/events/KeyboardEvent.java index 56eb201..a531a4c 100644 --- a/src/mightypork/rogue/input/events/KeyboardEvent.java +++ b/src/mightypork/rogue/bus/events/KeyboardEvent.java @@ -1,4 +1,4 @@ -package mightypork.rogue.input.events; +package mightypork.rogue.bus.events; import mightypork.utils.patterns.subscription.Handleable; diff --git a/src/mightypork/rogue/input/events/MouseButtonEvent.java b/src/mightypork/rogue/bus/events/MouseButtonEvent.java similarity index 97% rename from src/mightypork/rogue/input/events/MouseButtonEvent.java rename to src/mightypork/rogue/bus/events/MouseButtonEvent.java index 8aa7225..a877618 100644 --- a/src/mightypork/rogue/input/events/MouseButtonEvent.java +++ b/src/mightypork/rogue/bus/events/MouseButtonEvent.java @@ -1,4 +1,4 @@ -package mightypork.rogue.input.events; +package mightypork.rogue.bus.events; import mightypork.utils.math.coord.Coord; diff --git a/src/mightypork/rogue/input/events/MouseMotionEvent.java b/src/mightypork/rogue/bus/events/MouseMotionEvent.java similarity index 95% rename from src/mightypork/rogue/input/events/MouseMotionEvent.java rename to src/mightypork/rogue/bus/events/MouseMotionEvent.java index 0d36624..b2be021 100644 --- a/src/mightypork/rogue/input/events/MouseMotionEvent.java +++ b/src/mightypork/rogue/bus/events/MouseMotionEvent.java @@ -1,4 +1,4 @@ -package mightypork.rogue.input.events; +package mightypork.rogue.bus.events; import mightypork.utils.math.coord.Coord; diff --git a/src/mightypork/rogue/display/events/ScreenChangeEvent.java b/src/mightypork/rogue/bus/events/ScreenChangeEvent.java similarity index 95% rename from src/mightypork/rogue/display/events/ScreenChangeEvent.java rename to src/mightypork/rogue/bus/events/ScreenChangeEvent.java index b3e881e..65ec112 100644 --- a/src/mightypork/rogue/display/events/ScreenChangeEvent.java +++ b/src/mightypork/rogue/bus/events/ScreenChangeEvent.java @@ -1,4 +1,4 @@ -package mightypork.rogue.display.events; +package mightypork.rogue.bus.events; import mightypork.utils.math.coord.Coord; diff --git a/src/mightypork/rogue/display/events/UpdateEvent.java b/src/mightypork/rogue/bus/events/UpdateEvent.java similarity index 91% rename from src/mightypork/rogue/display/events/UpdateEvent.java rename to src/mightypork/rogue/bus/events/UpdateEvent.java index 3852170..9a29dd6 100644 --- a/src/mightypork/rogue/display/events/UpdateEvent.java +++ b/src/mightypork/rogue/bus/events/UpdateEvent.java @@ -1,4 +1,4 @@ -package mightypork.rogue.display.events; +package mightypork.rogue.bus.events; import mightypork.utils.patterns.subscription.Handleable; diff --git a/src/mightypork/rogue/display/DisplaySystem.java b/src/mightypork/rogue/display/DisplaySystem.java index 0510543..3d10f64 100644 --- a/src/mightypork/rogue/display/DisplaySystem.java +++ b/src/mightypork/rogue/display/DisplaySystem.java @@ -7,9 +7,9 @@ import java.awt.image.BufferedImage; import java.nio.ByteBuffer; import mightypork.rogue.AppAccess; -import mightypork.rogue.AppSubsystem; -import mightypork.rogue.display.constraints.ConstraintContext; -import mightypork.rogue.display.events.ScreenChangeEvent; +import mightypork.rogue.bus.DelegatingBusClient; +import mightypork.rogue.bus.events.ScreenChangeEvent; +import mightypork.rogue.display.constraints.Bounding; import mightypork.utils.logging.Log; import mightypork.utils.math.coord.Coord; import mightypork.utils.math.coord.Rect; @@ -20,7 +20,7 @@ import org.lwjgl.opengl.Display; import org.lwjgl.opengl.DisplayMode; -public class DisplaySystem extends AppSubsystem implements ConstraintContext { +public class DisplaySystem extends DelegatingBusClient implements Bounding { private DisplayMode windowDisplayMode; private int targetFps; @@ -51,7 +51,7 @@ public class DisplaySystem extends AppSubsystem implements ConstraintContext { */ private void initChannels() { - msgbus().createChannel(ScreenChangeEvent.class, ScreenChangeEvent.Listener.class); + bus().createChannel(ScreenChangeEvent.class, ScreenChangeEvent.Listener.class); } @@ -101,7 +101,7 @@ public class DisplaySystem extends AppSubsystem implements ConstraintContext { Display.update(); } - msgbus().broadcast(new ScreenChangeEvent(true, Display.isFullscreen(), getSize())); + bus().broadcast(new ScreenChangeEvent(true, Display.isFullscreen(), getSize())); } catch (Throwable t) { Log.e("Failed to toggle fullscreen mode.", t); @@ -178,7 +178,7 @@ public class DisplaySystem extends AppSubsystem implements ConstraintContext { public void beginFrame() { if (Display.wasResized()) { - msgbus().broadcast(new ScreenChangeEvent(false, Display.isFullscreen(), getSize())); + bus().broadcast(new ScreenChangeEvent(false, Display.isFullscreen(), getSize())); } glLoadIdentity(); diff --git a/src/mightypork/rogue/display/Screen.java b/src/mightypork/rogue/display/Screen.java index c651849..b8e15fb 100644 --- a/src/mightypork/rogue/display/Screen.java +++ b/src/mightypork/rogue/display/Screen.java @@ -3,9 +3,9 @@ package mightypork.rogue.display; import static org.lwjgl.opengl.GL11.*; import mightypork.rogue.AppAccess; -import mightypork.rogue.AppSubsystem; -import mightypork.rogue.display.constraints.ConstraintContext; -import mightypork.rogue.display.events.ScreenChangeEvent; +import mightypork.rogue.bus.DelegatingBusClient; +import mightypork.rogue.bus.events.ScreenChangeEvent; +import mightypork.rogue.display.constraints.Bounding; import mightypork.rogue.input.KeyBinder; import mightypork.rogue.input.KeyBindingPool; import mightypork.rogue.input.KeyStroke; @@ -20,7 +20,7 @@ import mightypork.utils.math.coord.Rect; * * @author MightyPork */ -public abstract class Screen extends AppSubsystem implements KeyBinder, ConstraintContext, ScreenChangeEvent.Listener { +public abstract class Screen extends DelegatingBusClient implements KeyBinder, Bounding, ScreenChangeEvent.Listener { private KeyBindingPool keybindings; @@ -28,7 +28,10 @@ public abstract class Screen extends AppSubsystem implements KeyBinder, Constrai public Screen(AppAccess app) { - super(app, false); + super(app, true); + + // disable events initially + enableEvents(false); } diff --git a/src/mightypork/rogue/display/ScreenTestAnimations.java b/src/mightypork/rogue/display/ScreenTestAnimations.java index 99bd5f3..18d2815 100644 --- a/src/mightypork/rogue/display/ScreenTestAnimations.java +++ b/src/mightypork/rogue/display/ScreenTestAnimations.java @@ -4,8 +4,8 @@ package mightypork.rogue.display; import java.util.Random; import mightypork.rogue.AppAccess; +import mightypork.rogue.bus.events.MouseButtonEvent; import mightypork.rogue.input.KeyStroke; -import mightypork.rogue.input.events.MouseButtonEvent; import mightypork.rogue.util.RenderUtils; import mightypork.utils.math.Polar; import mightypork.utils.math.color.RGB; diff --git a/src/mightypork/rogue/display/constraints/ConstraintContext.java b/src/mightypork/rogue/display/constraints/Bounding.java similarity index 69% rename from src/mightypork/rogue/display/constraints/ConstraintContext.java rename to src/mightypork/rogue/display/constraints/Bounding.java index aa5b86a..4a55b55 100644 --- a/src/mightypork/rogue/display/constraints/ConstraintContext.java +++ b/src/mightypork/rogue/display/constraints/Bounding.java @@ -5,11 +5,11 @@ import mightypork.utils.math.coord.Rect; /** - * Constraints can be based on this + * Bounding box provider - context for {@link Constraint} * * @author MightyPork */ -public interface ConstraintContext { +public interface Bounding { /** * @return bounding rectangle diff --git a/src/mightypork/rogue/display/constraints/Constraint.java b/src/mightypork/rogue/display/constraints/Constraint.java index ba85052..d03153e 100644 --- a/src/mightypork/rogue/display/constraints/Constraint.java +++ b/src/mightypork/rogue/display/constraints/Constraint.java @@ -2,39 +2,58 @@ package mightypork.rogue.display.constraints; import mightypork.utils.math.coord.Coord; +import mightypork.utils.math.coord.Rect; -public abstract class Constraint implements ConstraintContext { +public abstract class Constraint implements Bounding { - protected ConstraintContext context; + protected Bounding context; - public Constraint(ConstraintContext context) { + public Constraint(Bounding context) { this.context = context; } - public void setContext(ConstraintContext context) + /** + * Assign a context + * + * @param context + */ + public void setContext(Bounding context) { this.context = context; } - public ConstraintContext getContext() + /** + * @return context + */ + public Bounding getContext() { return context; } + /** + * @return context rect origin + */ protected Coord origin() { return context.getRect().getOrigin(); } + /** + * @return context rect size + */ protected Coord size() { return context.getRect().getSize(); } + + @Override + public abstract Rect getRect(); + } diff --git a/src/mightypork/rogue/display/rendering/Renderable.java b/src/mightypork/rogue/display/rendering/Renderable.java new file mode 100644 index 0000000..8f20e85 --- /dev/null +++ b/src/mightypork/rogue/display/rendering/Renderable.java @@ -0,0 +1,8 @@ +package mightypork.rogue.display.rendering; + + +public interface Renderable { + + public void render(); + +} diff --git a/src/mightypork/rogue/display/rendering/ScreenLayer.java b/src/mightypork/rogue/display/rendering/ScreenLayer.java new file mode 100644 index 0000000..8254f9f --- /dev/null +++ b/src/mightypork/rogue/display/rendering/ScreenLayer.java @@ -0,0 +1,18 @@ +package mightypork.rogue.display.rendering; + + +import mightypork.rogue.AppAccess; +import mightypork.rogue.bus.DelegatingBusClient; + + +public abstract class ScreenLayer extends DelegatingBusClient implements Renderable { + + public ScreenLayer(AppAccess app) { + super(app, true); + } + + + @Override + public abstract void render(); + +} diff --git a/src/mightypork/rogue/input/InputSystem.java b/src/mightypork/rogue/input/InputSystem.java index 2b6af67..6f6d4cd 100644 --- a/src/mightypork/rogue/input/InputSystem.java +++ b/src/mightypork/rogue/input/InputSystem.java @@ -2,10 +2,10 @@ package mightypork.rogue.input; import mightypork.rogue.AppAccess; -import mightypork.rogue.AppSubsystem; -import mightypork.rogue.input.events.KeyboardEvent; -import mightypork.rogue.input.events.MouseButtonEvent; -import mightypork.rogue.input.events.MouseMotionEvent; +import mightypork.rogue.bus.DelegatingBusClient; +import mightypork.rogue.bus.events.KeyboardEvent; +import mightypork.rogue.bus.events.MouseButtonEvent; +import mightypork.rogue.bus.events.MouseMotionEvent; import mightypork.utils.math.coord.Coord; import org.lwjgl.LWJGLException; @@ -14,7 +14,7 @@ import org.lwjgl.input.Mouse; import org.lwjgl.opengl.Display; -public class InputSystem extends AppSubsystem implements KeyBinder { +public class InputSystem extends DelegatingBusClient implements KeyBinder { // listeners private KeyBindingPool keybindings; @@ -60,9 +60,9 @@ public class InputSystem extends AppSubsystem implements KeyBinder { private void initChannels() { - msgbus().createChannel(KeyboardEvent.class, KeyboardEvent.Listener.class); - msgbus().createChannel(MouseMotionEvent.class, MouseMotionEvent.Listener.class); - msgbus().createChannel(MouseButtonEvent.class, MouseButtonEvent.Listener.class); + bus().createChannel(KeyboardEvent.class, KeyboardEvent.Listener.class); + bus().createChannel(MouseMotionEvent.class, MouseMotionEvent.Listener.class); + bus().createChannel(MouseButtonEvent.class, MouseButtonEvent.Listener.class); } @@ -80,6 +80,21 @@ public class InputSystem extends AppSubsystem implements KeyBinder { } + @Override + public void update(double delta) + { + Display.processMessages(); + + while (Mouse.next()) { + onMouseEvent(); + } + + while (Keyboard.next()) { + onKeyEvent(); + } + } + + private void onMouseEvent() { int button = Mouse.getEventButton(); @@ -88,8 +103,8 @@ public class InputSystem extends AppSubsystem implements KeyBinder { Coord move = new Coord(Mouse.getEventDX(), Mouse.getEventDY()); int wheeld = Mouse.getEventDWheel(); - if (button != -1 || wheeld != 0) msgbus().broadcast(new MouseButtonEvent(pos, button, down, wheeld)); - if (!move.isZero()) msgbus().broadcast(new MouseMotionEvent(pos, move)); + if (button != -1 || wheeld != 0) bus().broadcast(new MouseButtonEvent(pos, button, down, wheeld)); + if (!move.isZero()) bus().broadcast(new MouseMotionEvent(pos, move)); } @@ -98,21 +113,6 @@ public class InputSystem extends AppSubsystem implements KeyBinder { int key = Keyboard.getEventKey(); boolean down = Keyboard.getEventKeyState(); char c = Keyboard.getEventCharacter(); - msgbus().broadcast(new KeyboardEvent(key, c, down)); - } - - - @Override - public void update(double delta) - { - Display.processMessages(); // redundant if Display.update() is called in main loop - - while (Mouse.next()) { - onMouseEvent(); - } - - while (Keyboard.next()) { - onKeyEvent(); - } + bus().broadcast(new KeyboardEvent(key, c, down)); } } diff --git a/src/mightypork/rogue/input/KeyBinding.java b/src/mightypork/rogue/input/KeyBinding.java index 9af594c..d334fe1 100644 --- a/src/mightypork/rogue/input/KeyBinding.java +++ b/src/mightypork/rogue/input/KeyBinding.java @@ -1,7 +1,7 @@ package mightypork.rogue.input; -import mightypork.rogue.input.events.KeyboardEvent; +import mightypork.rogue.bus.events.KeyboardEvent; public class KeyBinding implements KeyboardEvent.Listener { diff --git a/src/mightypork/rogue/input/KeyBindingPool.java b/src/mightypork/rogue/input/KeyBindingPool.java index 2951f98..cc98703 100644 --- a/src/mightypork/rogue/input/KeyBindingPool.java +++ b/src/mightypork/rogue/input/KeyBindingPool.java @@ -5,7 +5,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Set; -import mightypork.rogue.input.events.KeyboardEvent; +import mightypork.rogue.bus.events.KeyboardEvent; import mightypork.utils.logging.Log; diff --git a/src/mightypork/rogue/sounds/SoundSystem.java b/src/mightypork/rogue/sounds/SoundSystem.java index 4ca9811..c3a2718 100644 --- a/src/mightypork/rogue/sounds/SoundSystem.java +++ b/src/mightypork/rogue/sounds/SoundSystem.java @@ -8,7 +8,7 @@ import java.util.Map; import java.util.Set; import mightypork.rogue.AppAccess; -import mightypork.rogue.AppSubsystem; +import mightypork.rogue.bus.DelegatingBusClient; import mightypork.utils.logging.Log; import mightypork.utils.math.Calc.Buffers; import mightypork.utils.math.coord.Coord; @@ -25,7 +25,7 @@ import org.newdawn.slick.openal.SoundStore; * @author MightyPork */ @SuppressWarnings("unchecked") -public class SoundSystem extends AppSubsystem { +public class SoundSystem extends DelegatingBusClient { private static final Coord INITIAL_LISTENER_POS = new Coord(0, 0, 0); private static final int MAX_SOURCES = 256; diff --git a/src/mightypork/rogue/testing/TestConstraints.java b/src/mightypork/rogue/testing/TestConstraints.java index 5823d28..d56ad4f 100644 --- a/src/mightypork/rogue/testing/TestConstraints.java +++ b/src/mightypork/rogue/testing/TestConstraints.java @@ -1,8 +1,8 @@ package mightypork.rogue.testing; +import mightypork.rogue.display.constraints.Bounding; import mightypork.rogue.display.constraints.Constraint; -import mightypork.rogue.display.constraints.ConstraintContext; import mightypork.utils.math.coord.Coord; import mightypork.utils.math.coord.Rect; @@ -11,7 +11,7 @@ public class TestConstraints { public static void main(String[] args) { - ConstraintContext context = new ConstraintContext() { + Bounding context = new Bounding() { @Override public Rect getRect() @@ -25,7 +25,7 @@ public class TestConstraints { private double height; - public Navbar(ConstraintContext context, double height) { + public Navbar(Bounding context, double height) { super(context); this.height = height; } @@ -44,7 +44,7 @@ public class TestConstraints { private int tile; - public TileHorizontal(ConstraintContext context, int tileCount, int aTile) { + public TileHorizontal(Bounding context, int tileCount, int aTile) { super(context); this.count = tileCount; setTile(aTile); diff --git a/src/mightypork/rogue/testing/TestMsgbus.java b/src/mightypork/rogue/testing/TestMsgbus.java new file mode 100644 index 0000000..540b52d --- /dev/null +++ b/src/mightypork/rogue/testing/TestMsgbus.java @@ -0,0 +1,219 @@ +package mightypork.rogue.testing; + + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import mightypork.utils.logging.Log; +import mightypork.utils.patterns.subscription.Handleable; +import mightypork.utils.patterns.subscription.MessageBus; +import mightypork.utils.patterns.subscription.clients.DelegatingClient; +import mightypork.utils.patterns.subscription.clients.ToggleableClient; + + +public class TestMsgbus { + + public static void main(String[] args) + { + Log.create("runtime", new File("."), 0); + + MessageBus bus = new MessageBus(); + + bus.createChannel(StringMessage.class, StringMessage.Listener.class); + bus.createChannel(IntMessage.class, IntMessage.Listener.class); + + Delegator deleg1 = new Delegator("Deleg1"); + Delegator deleg2 = new Delegator("Deleg2"); + Toggleable togg1 = new Toggleable("Tog1"); + Toggleable togg2 = new Toggleable("Tog2"); + Toggleable plain1 = new Toggleable("Plain1"); + Toggleable plain2 = new Toggleable("Plain2"); + Toggleable plain3 = new Toggleable("Plain3"); + + PlainInt pint = new PlainInt("Ints"); + PlainBoth pboth = new PlainBoth("Both"); + + bus.subscribe(deleg1); + + deleg1.clients.add(togg1); + deleg1.clients.add(plain2); + deleg1.clients.add(deleg2); + deleg1.clients.add(pint); + + deleg2.clients.add(deleg1); + deleg2.clients.add(togg1); + deleg2.clients.add(plain3); + deleg2.clients.add(pboth); + + bus.subscribe(plain1); + + bus.subscribe(togg2); + + bus.broadcast(new StringMessage("")); + bus.broadcast(new IntMessage(7)); + bus.broadcast(new IntMessage(13)); + + deleg2.delegating = false; + + bus.broadcast(new IntMessage(44)); + + deleg2.delegating = true; + + bus.broadcast(new IntMessage(45)); + + } + +} + + +class Delegator extends Plain implements DelegatingClient { + + List clients = new ArrayList(); + boolean delegating = true; + + + public Delegator(String name) { + super(name); + } + + + @Override + public Collection getChildClients() + { + return clients; + } + + + @Override + public boolean doesDelegate() + { + return delegating; + } +} + + +class Toggleable extends Plain implements ToggleableClient { + + boolean subscribing = true; + + + public Toggleable(String name) { + super(name); + } + + + @Override + public boolean doesSubscribe() + { + return subscribing; + } +} + + +class Plain implements StringMessage.Listener { + + String name; + + + public Plain(String name) { + this.name = name; + } + + + @Override + public void receive(StringMessage message) + { + System.out.println(name + " (STR) RECV: " + message.s); + } +} + + +class PlainInt implements IntMessage.Listener { + + String name; + + + public PlainInt(String name) { + this.name = name; + } + + + @Override + public void receive(IntMessage message) + { + System.out.println(name + " (INT) RECV: " + message.i); + } +} + + +class PlainBoth implements IntMessage.Listener, StringMessage.Listener { + + String name; + + + public PlainBoth(String name) { + this.name = name; + } + + + @Override + public void receive(IntMessage message) + { + System.out.println(name + " (both-INT) RECV: " + message.i); + } + + + @Override + public void receive(StringMessage message) + { + System.out.println(name + " (both-STR) RECV: " + message.s); + } +} + + +class StringMessage implements Handleable { + + String s; + + + StringMessage(String str) { + this.s = str; + } + + public interface Listener { + + public void receive(StringMessage message); + } + + + @Override + public void handleBy(Listener handler) + { + handler.receive(this); + } +} + + +class IntMessage implements Handleable { + + int i; + + + IntMessage(int i) { + this.i = i; + } + + public interface Listener { + + public void receive(IntMessage message); + } + + + @Override + public void handleBy(Listener handler) + { + handler.receive(this); + } +} diff --git a/src/mightypork/rogue/textures/Textures.java b/src/mightypork/rogue/textures/Textures.java index e6fc1c9..d67ebb5 100644 --- a/src/mightypork/rogue/textures/Textures.java +++ b/src/mightypork/rogue/textures/Textures.java @@ -6,6 +6,8 @@ import static org.lwjgl.opengl.GL11.*; import org.newdawn.slick.opengl.Texture; +// TODO rewrite to use hashmap with keys + /** * Texture loading class * @@ -13,62 +15,15 @@ import org.newdawn.slick.opengl.Texture; */ public class Textures { - protected static Texture buttons_menu; - protected static Texture buttons_small; - - // patterns need raw access (are repeated) - public static Texture steel_small_dk; - public static Texture steel_small_lt; - public static Texture steel_big_dk; - public static Texture steel_big_lt; - public static Texture steel_big_scratched; - public static Texture steel_brushed; - public static Texture steel_rivet_belts; - public static Texture water; - - // particles need direct access - public static Texture snow; - public static Texture leaves; - public static Texture rain; - public static Texture circle; - protected static Texture logo; - protected static Texture widgets; - public static Texture program; - protected static Texture icons; - - public static Texture map_tiles; - public static Texture map_shading; - - // spotted - public static Texture turtle_blue; - public static Texture turtle_green; - public static Texture turtle_purple; - public static Texture turtle_yellow; - - // misc - public static Texture turtle_red; - public static Texture turtle_brown_dk; - public static Texture turtle_brown_lt; - public static Texture turtle_brown_orn; - // a shadow sheet - public static Texture turtle_shadows; - - public static Texture food; - public static Texture food_shadows; private static final String GUI = "res/images/gui/"; - private static final String PATTERNS = "res/images/patterns/"; - private static final String PROGRAM = "res/images/program/"; - private static final String TILES = "res/images/tiles/"; - private static final String SPRITES = "res/images/sprites/"; - private static final String PARTICLES = "res/images/particles/"; /** * Load what's needed for splash */ - public static void loadForSplash() + public static void load() { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); @@ -76,63 +31,9 @@ public class Textures { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); logo = TextureManager.load(GUI + "logo.png"); - steel_small_dk = TextureManager.load(PATTERNS + "steel.png"); Tx.initForSplash(); - } - - - /** - * Load textures - */ - public static void load() - { - // gui - buttons_menu = TextureManager.load(GUI + "buttons_menu.png"); - buttons_small = TextureManager.load(GUI + "buttons_small.png"); - widgets = TextureManager.load(GUI + "widgets.png"); - icons = TextureManager.load(GUI + "icons.png"); - - // patterns - - steel_small_lt = TextureManager.load(PATTERNS + "steel_light.png"); // XXX Unused texture - steel_big_dk = TextureManager.load(PATTERNS + "steel_big.png"); - steel_big_lt = TextureManager.load(PATTERNS + "steel_big_light.png"); - steel_big_scratched = TextureManager.load(PATTERNS + "steel_big_scratched.png"); - steel_brushed = TextureManager.load(PATTERNS + "steel_brushed.png"); - steel_rivet_belts = TextureManager.load(PATTERNS + "rivet_belts.png"); - water = TextureManager.load(PATTERNS + "water.png"); - - // particles - snow = TextureManager.load(PARTICLES + "snow.png"); - leaves = TextureManager.load(PARTICLES + "leaves.png"); - rain = TextureManager.load(PARTICLES + "rain.png"); - circle = TextureManager.load(PARTICLES + "circle.png"); - - // program tiles - program = TextureManager.load(PROGRAM + "program.png"); - - // map tiles - map_tiles = TextureManager.load(TILES + "tiles.png"); - map_shading = TextureManager.load(TILES + "shading.png"); - - // turtle - turtle_blue = TextureManager.load(SPRITES + "HD-blue.png"); - turtle_yellow = TextureManager.load(SPRITES + "HD-yellow.png"); - turtle_green = TextureManager.load(SPRITES + "HD-green.png"); - turtle_purple = TextureManager.load(SPRITES + "HD-purple.png"); - turtle_red = TextureManager.load(SPRITES + "HD-red.png"); - - turtle_brown_dk = TextureManager.load(SPRITES + "HD-brown-dk.png"); - turtle_brown_lt = TextureManager.load(SPRITES + "HD-brown-lt.png"); - turtle_brown_orn = TextureManager.load(SPRITES + "HD-brown-ornamental.png"); - turtle_shadows = TextureManager.load(SPRITES + "HD-shadow.png"); - - food = TextureManager.load(SPRITES + "food.png"); - food_shadows = TextureManager.load(SPRITES + "food-shadow.png"); - glDisable(GL_TEXTURE_2D); - Tx.init(); } diff --git a/src/mightypork/rogue/textures/Tx.java b/src/mightypork/rogue/textures/Tx.java index 7715824..2f21abd 100644 --- a/src/mightypork/rogue/textures/Tx.java +++ b/src/mightypork/rogue/textures/Tx.java @@ -1,6 +1,8 @@ package mightypork.rogue.textures; +// TODO rewrite + /** * List of texture quads for GUIs * diff --git a/src/mightypork/utils/logging/Log.java b/src/mightypork/utils/logging/Log.java index ba7c858..b8ae96c 100644 --- a/src/mightypork/utils/logging/Log.java +++ b/src/mightypork/utils/logging/Log.java @@ -141,6 +141,26 @@ public class Log { } + /** + * Create a logger. If this is the first logger made, then it'll be made + * available via the static methods.
+ * Old logs will be kept. + * + * @param logName log name (used for filename, must be application-unique) + * @param logsDir directory to store logs in + * @return the created Log instance + */ + public static synchronized LogInstance create(String logName, File logsDir) + { + if (logs.containsKey(logName)) return logs.get(logName); + LogInstance log = new LogInstance(logName, logsDir, -1); + if (main == null) main = log; + logs.put(logName, log); + + return log; + } + + public int addMonitor(LogMonitor mon) { if (main == null) throw new IllegalStateException("Main logger not initialized."); diff --git a/src/mightypork/utils/logging/LogInstance.java b/src/mightypork/utils/logging/LogInstance.java index a0447c3..e874424 100644 --- a/src/mightypork/utils/logging/LogInstance.java +++ b/src/mightypork/utils/logging/LogInstance.java @@ -99,19 +99,26 @@ public class LogInstance { private void cleanup() { + if (logs_to_keep == 0) return; // overwrite + for (File f : FileUtils.listDirectory(file.getParentFile())) { if (!f.isFile()) continue; if (f.equals(file)) { Date d = new Date(f.lastModified()); - String fname = name + '_' + (new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss")).format(d) + getSuffix(); + String fbase = name + '_' + (new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss")).format(d); + String suff = getSuffix(); + String cntStr = ""; + File f2; - File f2 = new File(dir, fname); + for (int cnt = 0; (f2 = new File(dir, fbase + cntStr + suff)).exists(); cntStr = "_" + (++cnt)); f.renameTo(f2); } } + if (logs_to_keep == -1) return; // keep all + List oldLogs = FileUtils.listDirectory(dir, new FileFilter() { @Override diff --git a/src/mightypork/utils/objects/VarargsParser.java b/src/mightypork/utils/objects/VarargsParser.java index a9bfcfa..96c0ec1 100644 --- a/src/mightypork/utils/objects/VarargsParser.java +++ b/src/mightypork/utils/objects/VarargsParser.java @@ -18,6 +18,9 @@ import java.util.Map; * * * + * + * + * * Object[] array = { "one", 1, "two", 4, "three", 9, "four", 16 }; * Map<String, Integer> args = new VarargsParser<String, Integer>().parse(array); * diff --git a/src/mightypork/utils/patterns/subscription/MessageBus.java b/src/mightypork/utils/patterns/subscription/MessageBus.java index 1333b00..aa43699 100644 --- a/src/mightypork/utils/patterns/subscription/MessageBus.java +++ b/src/mightypork/utils/patterns/subscription/MessageBus.java @@ -1,8 +1,8 @@ package mightypork.utils.patterns.subscription; +import java.util.Collection; import java.util.LinkedHashSet; -import java.util.Set; import mightypork.utils.logging.Log; @@ -12,10 +12,12 @@ import mightypork.utils.logging.Log; * * @author MightyPork */ -public class MessageBus implements Subscribable { +final public class MessageBus { + + private Collection> channels = new LinkedHashSet>(); + + private Collection clients = new LinkedHashSet(); - private Set> channels = new LinkedHashSet>(); - private Set clients = new LinkedHashSet(); private boolean warn_unsent = true; @@ -38,10 +40,6 @@ public class MessageBus implements Subscribable { channels.add(channel); - for (Object c : clients) { - channel.addSubscriber(c); - } - return channel; } @@ -54,10 +52,6 @@ public class MessageBus implements Subscribable { public void removeChannel(MessageChannel channel) { channels.remove(channel); - - for (Object c : clients) { - channel.removeSubscriber(c); - } } @@ -70,9 +64,11 @@ public class MessageBus implements Subscribable { public boolean broadcast(Object message) { boolean sent = false; + for (MessageChannel b : channels) { - sent |= b.broadcast(message); + sent |= b.broadcast(message, clients); } + if (!sent && warn_unsent) Log.w("Message not accepted by any channel: " + message); return sent; @@ -80,33 +76,29 @@ public class MessageBus implements Subscribable { /** - * Subscribe a client to the bus. The client will be connected to all - * current and future channels, until removed from the bus. + * 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 * @return true on success */ - @Override - public boolean addSubscriber(Object client) + public boolean subscribe(Object client) { if (client == null) return false; - for (MessageChannel b : channels) { - b.addSubscriber(client); - } - clients.add(client); return true; } - @Override - public void removeSubscriber(Object client) + /** + * Disconnect a client from the bus. + * + * @param client the client + */ + public void unsubscribe(Object client) { - for (MessageChannel b : channels) { - b.removeSubscriber(client); - } - clients.remove(client); } diff --git a/src/mightypork/utils/patterns/subscription/MessageChannel.java b/src/mightypork/utils/patterns/subscription/MessageChannel.java index f3a41f6..3a94993 100644 --- a/src/mightypork/utils/patterns/subscription/MessageChannel.java +++ b/src/mightypork/utils/patterns/subscription/MessageChannel.java @@ -1,22 +1,22 @@ package mightypork.utils.patterns.subscription; +import java.util.Collection; import java.util.HashSet; -import java.util.Set; + +import mightypork.utils.logging.Log; +import mightypork.utils.patterns.subscription.clients.DelegatingClient; +import mightypork.utils.patterns.subscription.clients.ToggleableClient; /** - * Message subsystem (broadcaster with clients) to which clients can subscribe.
- * If more than one type of message is needed, {@link MessageBus} is a better - * choice. + * Message channel, module of {@link MessageBus} * * @author MightyPork * @param message type * @param client (subscriber) type */ -public final class MessageChannel, CLIENT> implements Subscribable { - - private Set clients = new HashSet(); +final public class MessageChannel, CLIENT> { private Class clientClass; private Class messageClass; @@ -24,61 +24,74 @@ public final class MessageChannel, CLIENT> im public MessageChannel(Class messageClass, Class clientClass) { - if (messageClass == null || clientClass == null) throw new IllegalArgumentException("Null Message or Client class."); + if (messageClass == null || clientClass == null) throw new NullPointerException("Null Message or Client class."); this.clientClass = clientClass; this.messageClass = messageClass; } - @Override - public boolean addSubscriber(Object client) - { - if (!canSubscribe(client)) return false; - - clients.add(clientClass.cast(client)); - return true; - } - - - @Override - public void removeSubscriber(Object client) - { - clients.remove(client); - } - - /** * Try to broadcast a message.
* If message is of wrong type, false is returned. * - * @param message a message to send - * @return true if message was sent + * @param message a message to be sent + * @param clients collection of clients + * @return true if message was accepted by this channel */ - public boolean broadcast(Object message) + public boolean broadcast(Object message, Collection clients) { - if (!canBroadcast(message)) return false; MESSAGE evt = messageClass.cast(message); - for (CLIENT client : clients) { - sendTo(client, evt); - } + doBroadcast(evt, clients, new HashSet()); return true; } + private void doBroadcast(MESSAGE message, Collection clients, Collection processed) + { + for (Object client : clients) { + + // circular reference check + if (processed.contains(client)) { + Log.w("Client already served (subscribing twice?)"); + continue; + } + processed.add(client); + + // opt-out + if (client instanceof ToggleableClient) { + if (!((ToggleableClient) client).doesSubscribe()) { + continue; + } + } + + sendTo(client, message); + + if (client instanceof DelegatingClient) { + if (((DelegatingClient) client).doesDelegate()) { + doBroadcast(message, ((DelegatingClient) client).getChildClients(), processed); + } + } + } + } + + /** - * Send a message to a client + * Send a message to a client. * * @param client target client * @param message message to send */ - private void sendTo(CLIENT client, MESSAGE message) + @SuppressWarnings("unchecked") + private void sendTo(Object client, MESSAGE message) { - ((Handleable) message).handleBy(client); + if (clientClass.isInstance(client)) { + ((Handleable) message).handleBy((CLIENT) client); + } } @@ -86,34 +99,22 @@ public final class MessageChannel, CLIENT> im * Check if the given message can be broadcasted by this * {@link MessageChannel} * - * @param maybeMessage event object + * @param message event object * @return can be broadcasted */ - private boolean canBroadcast(Object maybeMessage) - { - return messageClass.isInstance(maybeMessage); - } - - - /** - * Check if a client can subscribe to this {@link MessageChannel} - * - * @param maybeClient client asking for subscription - * @return can subscribe - */ - public boolean canSubscribe(Object maybeClient) + public boolean canBroadcast(Object message) { - return clientClass.isInstance(maybeClient); + return message != null && messageClass.isInstance(message); } @Override public int hashCode() { - final int prime = 31; + final int prime = 13; int result = 1; - result = prime * result + clientClass.getName().hashCode(); - result = prime * result + messageClass.getName().hashCode(); + result = prime * result + ((clientClass == null) ? 0 : clientClass.hashCode()); + result = prime * result + ((messageClass == null) ? 0 : messageClass.hashCode()); return result; } @@ -124,13 +125,13 @@ public final class MessageChannel, CLIENT> im if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof MessageChannel)) return false; - MessageChannel other = (MessageChannel) obj; - - if (!clientClass.getName().equals(other.clientClass.getName())) return false; - - if (!messageClass.getName().equals(other.messageClass.getName())) return false; - + if (clientClass == null) { + if (other.clientClass != null) return false; + } else if (!clientClass.equals(other.clientClass)) return false; + if (messageClass == null) { + if (other.messageClass != null) return false; + } else if (!messageClass.equals(other.messageClass)) return false; return true; } diff --git a/src/mightypork/utils/patterns/subscription/Subscribable.java b/src/mightypork/utils/patterns/subscription/Subscribable.java deleted file mode 100644 index 3c539fd..0000000 --- a/src/mightypork/utils/patterns/subscription/Subscribable.java +++ /dev/null @@ -1,26 +0,0 @@ -package mightypork.utils.patterns.subscription; - - -/** - * Subscribable object - * - * @author MightyPork - */ -public interface Subscribable { - - /** - * Subscribe a client to messages from this object - * - * @param client a subscribing client - * @return true if client is now subscribed - */ - public boolean addSubscriber(Object client); - - - /** - * Unsubscribe a client from from this object - * - * @param client a clientto unsubscribe - */ - public void removeSubscriber(Object client); -} diff --git a/src/mightypork/utils/patterns/subscription/clients/DelegatingClient.java b/src/mightypork/utils/patterns/subscription/clients/DelegatingClient.java new file mode 100644 index 0000000..266b523 --- /dev/null +++ b/src/mightypork/utils/patterns/subscription/clients/DelegatingClient.java @@ -0,0 +1,25 @@ +package mightypork.utils.patterns.subscription.clients; + + +import java.util.Collection; + + +/** + * Client containing child clients + * + * @author MightyPork + */ +public interface DelegatingClient { + + /** + * @return collection of child clients. Can not be null. + */ + public Collection getChildClients(); + + + /** + * @return true if delegating is active + */ + public boolean doesDelegate(); + +} diff --git a/src/mightypork/utils/patterns/subscription/clients/ToggleableClient.java b/src/mightypork/utils/patterns/subscription/clients/ToggleableClient.java new file mode 100644 index 0000000..198556d --- /dev/null +++ b/src/mightypork/utils/patterns/subscription/clients/ToggleableClient.java @@ -0,0 +1,16 @@ +package mightypork.utils.patterns.subscription.clients; + + +/** + * Client that can toggle receiving messages. + * + * @author MightyPork + */ +public interface ToggleableClient { + + /** + * @return true if the client wants to receive messages + */ + public boolean doesSubscribe(); + +}