Refactoring of subsystems and eventbus

v5stable
Ondřej Hruška 10 years ago
parent 6013a105ad
commit 4a1a84fd87
  1. 3
      .gitignore
  2. 20
      README.md
  3. 0
      res/.gitkeep
  4. 30
      src/mightypork/rogue/App.java
  5. 11
      src/mightypork/rogue/AppAccess.java
  6. 61
      src/mightypork/rogue/AppAdapter.java
  7. 209
      src/mightypork/rogue/AppSubsystem.java
  8. 162
      src/mightypork/rogue/bus/DelegatingBusClient.java
  9. 23
      src/mightypork/rogue/bus/SimpleBusClient.java
  10. 26
      src/mightypork/rogue/bus/UpdateReceiver.java
  11. 2
      src/mightypork/rogue/bus/events/KeyboardEvent.java
  12. 2
      src/mightypork/rogue/bus/events/MouseButtonEvent.java
  13. 2
      src/mightypork/rogue/bus/events/MouseMotionEvent.java
  14. 2
      src/mightypork/rogue/bus/events/ScreenChangeEvent.java
  15. 2
      src/mightypork/rogue/bus/events/UpdateEvent.java
  16. 14
      src/mightypork/rogue/display/DisplaySystem.java
  17. 13
      src/mightypork/rogue/display/Screen.java
  18. 2
      src/mightypork/rogue/display/ScreenTestAnimations.java
  19. 4
      src/mightypork/rogue/display/constraints/Bounding.java
  20. 29
      src/mightypork/rogue/display/constraints/Constraint.java
  21. 8
      src/mightypork/rogue/display/rendering/Renderable.java
  22. 18
      src/mightypork/rogue/display/rendering/ScreenLayer.java
  23. 52
      src/mightypork/rogue/input/InputSystem.java
  24. 2
      src/mightypork/rogue/input/KeyBinding.java
  25. 2
      src/mightypork/rogue/input/KeyBindingPool.java
  26. 4
      src/mightypork/rogue/sounds/SoundSystem.java
  27. 8
      src/mightypork/rogue/testing/TestConstraints.java
  28. 219
      src/mightypork/rogue/testing/TestMsgbus.java
  29. 105
      src/mightypork/rogue/textures/Textures.java
  30. 2
      src/mightypork/rogue/textures/Tx.java
  31. 20
      src/mightypork/utils/logging/Log.java
  32. 11
      src/mightypork/utils/logging/LogInstance.java
  33. 3
      src/mightypork/utils/objects/VarargsParser.java
  34. 46
      src/mightypork/utils/patterns/subscription/MessageBus.java
  35. 119
      src/mightypork/utils/patterns/subscription/MessageChannel.java
  36. 26
      src/mightypork/utils/patterns/subscription/Subscribable.java
  37. 25
      src/mightypork/utils/patterns/subscription/clients/DelegatingClient.java
  38. 16
      src/mightypork/utils/patterns/subscription/clients/ToggleableClient.java

3
.gitignore vendored

@ -1,2 +1,3 @@
/bin/
/target/
/target/
*.log

@ -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

@ -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<br>
* 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;
}

@ -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<br>
* Destroy app & exit VM
*/
abstract void shutdown();
}

@ -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();
}
}

@ -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<Object> childSubscribers = new HashSet<Object>();
/**
* 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.<br>
* 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).<br>
*
* @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<br>
* (called during construction)
*/
protected abstract void init();
/**
* Deinitialize the subsystem<br>
* (called during destruction)<br>
* <br>
* All child eventbus subscribers will be removed from the eventbus.
*/
protected abstract void deinit();
/**
* Add a child subscriber to the {@link MessageBus}.<br>
* Child subscribers are removed when subsystem is destroyed, and can be
* connected/disconnected using the <code>enableEvents()</code> 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.");
}
}

@ -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<Object> childSubscribers = new HashSet<Object>();
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}.<br>
*
* @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<Object> getChildClients()
{
return childSubscribers;
}
@Override
public final boolean doesDelegate()
{
return doesSubscribe();
}
/**
* Set whether to receive {@link UpdateEvent}s (delta timing, one each
* frame).<br>
*
* @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<br>
* (called during construction)
*/
protected void init()
{
// no impl
}
/**
* Deinitialize the subsystem<br>
* (called during destruction)
*/
protected void deinit()
{
// no impl
}
}

@ -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);
}
}

@ -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);
}

@ -1,4 +1,4 @@
package mightypork.rogue.input.events;
package mightypork.rogue.bus.events;
import mightypork.utils.patterns.subscription.Handleable;

@ -1,4 +1,4 @@
package mightypork.rogue.input.events;
package mightypork.rogue.bus.events;
import mightypork.utils.math.coord.Coord;

@ -1,4 +1,4 @@
package mightypork.rogue.input.events;
package mightypork.rogue.bus.events;
import mightypork.utils.math.coord.Coord;

@ -1,4 +1,4 @@
package mightypork.rogue.display.events;
package mightypork.rogue.bus.events;
import mightypork.utils.math.coord.Coord;

@ -1,4 +1,4 @@
package mightypork.rogue.display.events;
package mightypork.rogue.bus.events;
import mightypork.utils.patterns.subscription.Handleable;

@ -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();

@ -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);
}

@ -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;

@ -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

@ -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();
}

@ -0,0 +1,8 @@
package mightypork.rogue.display.rendering;
public interface Renderable {
public void render();
}

@ -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();
}

@ -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));
}
}

@ -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 {

@ -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;

@ -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;

@ -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);

@ -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("<MSG>"));
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<Object> clients = new ArrayList<Object>();
boolean delegating = true;
public Delegator(String name) {
super(name);
}
@Override
public Collection<Object> 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<StringMessage.Listener> {
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<IntMessage.Listener> {
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);
}
}

@ -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();
}

@ -1,6 +1,8 @@
package mightypork.rogue.textures;
// TODO rewrite
/**
* List of texture quads for GUIs
*

@ -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.<br>
* 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.");

@ -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<File> oldLogs = FileUtils.listDirectory(dir, new FileFilter() {
@Override

@ -18,6 +18,9 @@ import java.util.Map;
*
*
*
*
*
*
* Object[] array = { &quot;one&quot;, 1, &quot;two&quot;, 4, &quot;three&quot;, 9, &quot;four&quot;, 16 };
* Map&lt;String, Integer&gt; args = new VarargsParser&lt;String, Integer&gt;().parse(array);
* </pre>

@ -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<MessageChannel<?, ?>> channels = new LinkedHashSet<MessageChannel<?, ?>>();
private Collection<Object> clients = new LinkedHashSet<Object>();
private Set<MessageChannel<?, ?>> channels = new LinkedHashSet<MessageChannel<?, ?>>();
private Set<Object> clients = new LinkedHashSet<Object>();
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);
}

@ -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.<br>
* 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> message type
* @param <CLIENT> client (subscriber) type
*/
public final class MessageChannel<MESSAGE extends Handleable<CLIENT>, CLIENT> implements Subscribable {
private Set<CLIENT> clients = new HashSet<CLIENT>();
final public class MessageChannel<MESSAGE extends Handleable<CLIENT>, CLIENT> {
private Class<CLIENT> clientClass;
private Class<MESSAGE> messageClass;
@ -24,61 +24,74 @@ public final class MessageChannel<MESSAGE extends Handleable<CLIENT>, CLIENT> im
public MessageChannel(Class<MESSAGE> messageClass, Class<CLIENT> 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.<br>
* If message is of wrong type, <code>false</code> 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<Object> clients)
{
if (!canBroadcast(message)) return false;
MESSAGE evt = messageClass.cast(message);
for (CLIENT client : clients) {
sendTo(client, evt);
}
doBroadcast(evt, clients, new HashSet<Object>());
return true;
}
private void doBroadcast(MESSAGE message, Collection<Object> clients, Collection<Object> 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<CLIENT>) message).handleBy(client);
if (clientClass.isInstance(client)) {
((Handleable<CLIENT>) message).handleBy((CLIENT) client);
}
}
@ -86,34 +99,22 @@ public final class MessageChannel<MESSAGE extends Handleable<CLIENT>, 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<MESSAGE extends Handleable<CLIENT>, 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;
}

@ -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);
}

@ -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<Object> getChildClients();
/**
* @return true if delegating is active
*/
public boolean doesDelegate();
}

@ -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();
}
Loading…
Cancel
Save