Package hermes

Source Code of hermes.World

package hermes;

import hermes.hshape.HShape;
import hermes.postoffice.KeySubscriber;
import hermes.postoffice.MouseSubscriber;
import hermes.postoffice.MouseWheelSubscriber;
import hermes.postoffice.OscSubscriber;
import hermes.postoffice.POCodes;
import hermes.postoffice.PostOffice;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Collection;



/**
* <p>The World defines a 'game state': it keeps track of all the Beings,
*   Groups, and their interaction. This is done through registration, HObjects and
*   Interactors must be registered with the World in order to be drawn or updated.</p>
* <p>The World is a <code>Thread</code>. When <code>start</code> is called, the World
* will automatically update all registered objects and apply all Interactors between
* them. This runs on the World's thread, which is separate from the Processing's draw
* thread. This multithreading can be bypassed by not calling <code>start</code>,
* and instead calling <code>update</code> within Processing's <code>draw</code> method.
* By default to update rate is locked to 60Hz. You can change this, or unlock the rate,
* by calling <code>lockUpdateRate</code> and <code>unlockUpdateRate</code>.</p>
* <p>There are two basic ways of working with a World. One is to set up all of your
* objects and Interactors outside of the World, and register them in Processing's <code>setup</code>.
* The other is to create a subclass of World, declare many variables inside this class,
* and override the empty World <code>setup</code> and <code>shutdown</code> methods. This allows for
* more control as additional computations can be done in the <code>preUpdate</code> and <code>postUpdate</code>
* methods, called by the thread before and after <code>update</code>, respectively.
*/
public class World extends Thread {
 
  protected PostOffice _postOffice; //post office

  // these hold add and delete operations until the end of the update
  private LinkedList<Pair<HObject,GenericGroup<?,?>>> _addQueue;
  private LinkedList<Pair<HObject,GenericGroup<?,?>>> _removeQueue;
  private LinkedList<HObject> _removeFromAllGroupsQueue;
 
  private Group<Being> _masterGroup; //this is the group used by the camera
  private Group<Being> _updateGroup;
 
  protected HCamera _camera; // the camera
  private Group<HCamera> _cameraGroup; // a Group with only one member: _camera (required to register an Interaction)
  private boolean _active = false; // whether the world is currently running -
 
  @SuppressWarnings("rawtypes")
  private List<Interaction> _interactions; // used to hold all the interactions we need to check
  private LinkedList<GenericGroup<?,?>> _groupsToUpdate; //used to hold all the being groups to be updated individually
 
  private long _updateLength = 0;
 
  /**
   * Instantiates the world with a PostOffice to handle I/O and a Camera to handle drawing.
   * @param postOffice  the PostOffice that will handle mouse, keyboard and OSC I/O
   * @param view      the camera that will be used for drawing
   */
  @SuppressWarnings("rawtypes")
  public World(PostOffice postOffice, HCamera view) {
   
    assert postOffice != null : "World constructor: postOffice must be a valid PostOffice";
    assert view != null : "World constructor: camera must be a valid Camera";
   
    //set the World's PApplet to the one set in Hermes
    _postOffice = postOffice;
    _camera = view;
   
    _interactions = new LinkedList<Interaction>();
    _addQueue = new LinkedList<Pair<HObject,GenericGroup<?,?>>>();
    _removeQueue = new LinkedList<Pair<HObject,GenericGroup<?,?>>>();
    _removeFromAllGroupsQueue = new LinkedList<HObject>();
    _groupsToUpdate = new LinkedList<GenericGroup<?,?>>();
   
    _masterGroup = new Group<Being>(this);
    _updateGroup = new Group<Being>(this);
   
    //initialize the Camera
    register(_camera, true);
    _cameraGroup = new Group<HCamera>(this);//make _cameraGroup
    _cameraGroup.add(_camera);//add _camera to _cameraGroup
    //register an Interaction between _cameraGroup and _masterGroup
    this.register(_cameraGroup, _masterGroup, new CameraBeingInteractor());

    lockUpdateRate(60); // lock the update rate to 60 updates/sec by default
  }
 
  // Simplified constructors
 
  /**
   * Constructor that automatically creates PostOffice and HCamera
   */
   public World() {
     this(new PostOffice(), new HCamera());
   }
  
   /**
    * Constructor that automatically creates PostOffice
    */
   public World(HCamera view) {
     this(new PostOffice(), view);
   }
  
   /**
    * Constructor that automatically creates PostOffice and HCamera, and sets up OSC
    */
    public World(int portIn, int portOut) {
      this(new PostOffice(portIn, portOut), new HCamera());
    }
   
    /**
     * Constructor that automatically creates PostOffice and sets up OSC
     */
    public World(int portIn, int portOut, HCamera view) {
      this(new PostOffice(portIn, portOut), view);
    }
   
    /**
     * Constructor that automatically creates HCamera
     */
    public World(PostOffice postOffice) {
      this(postOffice, new HCamera());
    }
 
  /**
   * Returns true after <code>start()</code> has been called.
   * @return  whether the world is currently running
   */
  public boolean isActive() {
    return _active;
  }
 
  /**
   * Tells the world to stop running.
   * Use this to terminate the world (not the deprecated <code>stop</code> method).
   */
  public void deactivate() {
    _active = false;
  }
 
  /**
   * Registers a being with the world, making it be drawn when it is on camera,
   *   its <code>update</code> method will be called by the loop if update is true.
   * @param being    the being to register
   * @param update  whether or not to update the being during the update loop
   */
  public void register(Being being, boolean update) {
    assert being != null : "World.register: being must be valid.";
   
    addToGroup(being, _masterGroup);
    if(update) {
      addToGroup(being, _updateGroup);
    }
  }
 
  /**
   * Shortcut for adding Being w/ update
   * @param being   the being to register
   */
  public void register(Being being) {
    register(being, true);
  }
 
  /**
   * Queues an HObject to be added to a group at the end of the current update.
   * @param object  the object to add
   * @param group    the group to add the object to
   */
  public void addToGroup(HObject object, GenericGroup<?,?> group) {
    _addQueue.addLast(new Pair<HObject,GenericGroup<?,?>>(object, group));
  }
 
  /**
   * Queues an HObject to be removed from a group at the end of the current update.
   * @param object    the object to remove
   * @param group      the group to add the object to
   */
  public void remove(HObject object, GenericGroup<?,?> group) {
    _removeQueue.addLast(new Pair<HObject,GenericGroup<?,?>>(object, group));
  }
 
  /**
   * Queues an HObject to be removed from all of the groups it is in at the end of the current update.
   * @param object  the object to delete
   */
  public void delete(HObject object) {
    _removeFromAllGroupsQueue.addLast(object);
  }
 
  /**
   * Resolves the add, remove, and delete queues, in that order.
   */
  public void resolveGroupQueues() {
    // resolve the add queue first
    for(Iterator<Pair<HObject,GenericGroup<?,?>>> iter = _addQueue.iterator(); iter.hasNext(); ) {
      Pair<HObject,GenericGroup<?,?>> pair = iter.next();
      pair.first.addToGroup(pair.second); // add being to the group
      iter.remove(); // remove the add from the queue
    }
    // resolve the remove queue
    for(Iterator<Pair<HObject,GenericGroup<?,?>>> iter = _removeQueue.iterator(); iter.hasNext(); ) {
      Pair<HObject,GenericGroup<?,?>> pair = iter.next();
      pair.first.removeFromGroup(pair.second); // add being to the group
      iter.remove(); // remove from the queue
    }
    // resolve the removeFromAllGroups queue
    for(Iterator<HObject> iter = _removeFromAllGroupsQueue.iterator(); iter.hasNext(); ) {
     
      HObject next = iter.next();
      _postOffice.removeAllSubscriptions(next);
      next.delete();
      iter.remove();
      /*
      iter.next().delete(); // delete the being
      iter.remove(); // remove from the queue
      */
    }
  }
 
  /**
   * Register an interaction to be handled on the update loop.
   * @param A          the first interacting group
   * @param B          the second interacting group
   * @param inter        the Interactor that detects and handles this interaction
   */
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public void register(GenericGroup A, GenericGroup B, Interactor inter) {
    if(A == B) {
      _interactions.add(new Interaction(A, B, inter, new SelfInteractionOptimizer()));
    } else {
      _interactions.add(new Interaction(A, B, inter, null));
    }
  }
 
  /**
   * Register an interaction to be handled on the update loop.
   * @param A      the first interacting group
   * @param B      the second interacting group
   * @param inter    the Interactor that detects and handles this interaction
   * @param optimizer  optimizer to use for the interaction
   */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  public void register(GenericGroup A, GenericGroup B, Interactor inter, Optimizer optimizer) {
    _interactions.add(new Interaction(A, B, inter, optimizer));
  }
 
  /**
   * register an interaction between A and all objects in B
   * @param A          an HObjects
   * @param B          a group
   * @param inter        the Interactor that detects and handles this interaction
   */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  public void register(HObject A, GenericGroup B, Interactor inter) {
    Group groupA = new Group(this);
    groupA.add(A);
    _interactions.add(new Interaction(groupA, B, inter, null));
  }
 
  /**
   * register an interaction between B and all objects in A
   * @param A          a group
   * @param B          a being
   * @param inter        the Interactor that detects and handles this interaction
   */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  public void register(GenericGroup A, HObject B, Interactor inter) {
    Group groupB = new Group(this);
    groupB.add(B);
    _interactions.add(new Interaction(A, groupB, inter, null));
  }
 
  /**
   * Register an interaction between HOjbects A and B.
   * @param A          the first object
   * @param B          the second object
   * @param inter        the interaction handler
   *                 upon detection or later
   */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  public void register(HObject A, HObject B, Interactor inter) {
    Group groupA = new Group(this);
    groupA.add(A);
    Group groupB = new Group(this);
    groupB.add(B);
    _interactions.add(new Interaction(groupA, groupB, inter, null));
  }
 
  /**
   * register a group to have its update called in the update loop
   * @param group    the group to update
   */
  @SuppressWarnings("rawtypes")
  public void register(GenericGroup group) {
    _groupsToUpdate.add(group);
  }
 
  /**
   * Registers a subscription to messages sent by a specific keyboard key.
   * @param sub  the KeySubscriber signing up
   * @param key  the code of the keyboard key whose messages the subscriber wants (use value from POConstants)
   */
  public void subscribe(KeySubscriber sub, int key) {
    _postOffice.subscribe(sub, key);
  }
 
  /**
   * Registers a subscription to messages sent by a specific keyboard key.
   * @param sub  the KeySubscriber signing up
   * @param key  the char of the keyboard key whose messages the subscriber wants
   */
  public void subscribe(KeySubscriber sub, char key) {
    _postOffice.subscribe(sub, key);
  }
 
  /**
   * Registers a subscription to messages sent by a specific mouse button.
   * <p>
   * Buttons are defined by constants in the POConstants class;
   * subscribe with "NO_BUTTON" to receive information about mouse movements when no button is pressed.
   * @param sub    the MouseSubscriber signing up
   * @param button  the code of the button whose messages the subscriber wants (use value from POContants)
   */
  public void subscribe(MouseSubscriber sub, POCodes.Button button) {
    _postOffice.subscribe(sub, button);
  }
 
  /**
   * A version of registerMouseSubscription that subscribes only to the requested button events
   *      that occur in the given region.
   * @param sub    the MouseSubscriber signing up
   * @param button  the code of the button whose messages the subscriber wants (use value from POContants)
   * @param region  the region on screen the subscriber wants to limit its subscription to
   */
    public void subscribe(MouseSubscriber sub, POCodes.Button button, HShape region) {
      _postOffice.subscribe(sub, button, region);
    }
 
  /**
   * Registers a subscription to the mouse wheel (one subscription gets you everything).
   * @param sub  the MouseWheelSubscriber signing up
   */
  public void subscribe(MouseWheelSubscriber sub) {
    _postOffice.subscribe(sub);
  }

  /**
   * Registers a subscription to messages received on a specific OSC address.
   * @param sub    the OscSubscriber signing up
   * @param address  the address whose messages the subscriber wants
   */
  public void subscribe(OscSubscriber sub, String address) {
    _postOffice.subscribe(sub, address);
  }

  /**
   * DO NOT CALL THIS METHOD. <br>
   * This starts the update loop, but this should be done by calling <code>World.start</code>
   *   instead, for threading purposes.
   */
  public void run() {
    setup();
    resolveGroupQueues();
    _active = true;
    while(_active) {
      preUpdate();
      update();
      postUpdate();
    }
    shutdown();
  }
 
  /**
   * Will be called once when the world is run, before the update loop.
   */
  public void setup() {}
 
  /**
   * Will be called once the world has finished running.
   */
  public void shutdown() {}
 
  /**
   * Will be executed on each loop before update is called.
   */
  public void preUpdate() {}
 
  /**
   * Will be executed on each loop after update is called.
   */
  public void postUpdate() {}
 
  /**
   * Executes the update loop. Should only be called manually if <code>start</code> has not been called
   * and threading is not desired.
   */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  public void update() {

    long time = System.currentTimeMillis();
   
    // 1. handle the message queue from the post office if post office is defined
    _postOffice.checkMail();
   
    // 3. go through the registered groups and update them
    for(Iterator<GenericGroup<?,?>> iter = _groupsToUpdate.iterator(); iter.hasNext(); ) {
      GenericGroup group = iter.next();
      group.update();
    }
   
    resolveGroupQueues();
   
    // 4. apply being updates
    List<Being> unresolvedUpdates = updateHelper(_updateGroup.getObjects());
   
    // 2. go through the registered interactions in order
    LinkedList<DetectedInteraction> detectedInteractionsQ = new LinkedList<DetectedInteraction>();
    for(Iterator<Interaction> iter = _interactions.iterator(); iter.hasNext(); ) {
      Interaction interaction = iter.next();
      InteractionHandler handler = new InteractionHandler(interaction, detectedInteractionsQ);
     
      if(interaction.getA().equals(_cameraGroup)) {
        _camera.collisionsReset();
      }
     
      Collection A = interaction.getA().getObjects();
      Collection B = interaction.getB().getObjects();     
      if(interaction.getOptimizer() == null) { // if this is a non-optimized interaction
        // perform the O(n^2) calculation on all the groups
        for(Iterator iterA = A.iterator(); iterA.hasNext(); ) {
          HObject being1 = (HObject)iterA.next();
          for(Iterator iterB = B.iterator(); iterB.hasNext(); ) {
            HObject being2 = (HObject)iterB.next();
            handler.interactionHandler(being1, being2);
          }
        }
      } else { // if this is an optimized interaction
        Optimizer optimizer = interaction.getOptimizer();
        optimizer.detect(interaction.getA(), interaction.getB(), handler);
      }
     
      if(interaction.getA().equals(_cameraGroup)) {
        _camera.collisionsComplete();
      }
    }
    //handle all detected interactions here (for not immediate interactions)
    for(Iterator<DetectedInteraction> iter = detectedInteractionsQ.iterator(); iter.hasNext();) {
      DetectedInteraction di = iter.next();
      Interactor interactor = di.get_interactor();
      HObject being1 = di.get_being1();
      HObject being2 = di.get_being2();
      synchronized(being1) {
        synchronized(being2) {
          interactor.handle(being1, being2);
        }
      }
    }
   
//    // 3. go through the registered groups and update them
//    for(Iterator<GenericGroup<?,?>> iter = _groupsToUpdate.iterator(); iter.hasNext(); ) {
//      GenericGroup group = iter.next();
//      group.update();
//    }
//   
//    resolveGroupQueues();
//   
//    // 4. apply being updates
//    List<Being> unresolvedUpdates = updateHelper(_updateGroup.getObjects());
   
    // deal with anything unresolved
    while(!unresolvedUpdates.isEmpty()) {
 
      // handle unresolved interactions
      detectedInteractionsQ = new LinkedList<DetectedInteraction>();
      for(Iterator<Interaction> iter = _interactions.iterator(); iter.hasNext(); ) {
        Interaction interaction = iter.next();
        // we only do an interaction if we need to
        if(!interaction.getInteractor().multisampled() ||
            (!interaction.getA().hasNeedsMoreSamples() && !interaction.getB().hasNeedsMoreSamples()))
          continue;
       
        InteractionHandler handler = new InteractionHandler(interaction, detectedInteractionsQ);
       
        if(interaction.getOptimizer() == null) { // if this is a non-optimized interaction
          for(Iterator iterA = interaction.getA().getNeedsMoreSamples(); iterA.hasNext(); ) {
            HObject object1 = (HObject)iterA.next();
            if(!object1.needsMoreSamples()) {
              // if A is done updating, remove it
              iterA.remove();
              continue;
            }
            for(Iterator iterB = interaction.getB().iterator(); iterB.hasNext(); ) {
              HObject object2 = (HObject)iterB.next();
              handler.interactionHandler(object1, object2);
            }
          }
          for(Iterator iterB = interaction.getB().getNeedsMoreSamples(); iterB.hasNext(); ) {
            HObject object2 = (HObject)iterB.next();
            if(!object2.needsMoreSamples()) {
              // if A is done updating, remove it
              iterB.remove();
              continue;
            }
            for(Iterator iterA = interaction.getA().iterator(); iterA.hasNext(); ) {
              HObject object1 = (HObject)iterA.next();
              if(object1.needsMoreSamples()) // if obj1 is multisampled its already been checked with obj2
                continue;
              handler.interactionHandler(object1, object2);
            }
          }
        } else { // if this is an optimized interaction
          Optimizer optimizer = interaction.getOptimizer();
          optimizer.detect(interaction.getA(), interaction.getB(), handler);
        }
       
        //handle all detected interactions here (for not immediate interactions)
        for(Iterator<DetectedInteraction> diIter = detectedInteractionsQ.iterator(); diIter.hasNext();) {
          DetectedInteraction di = diIter.next();
          Interactor interactor = di.get_interactor();
          HObject being1 = di.get_being1();
          HObject being2 = di.get_being2();
          synchronized(being1) {
            synchronized(being2) {
              interactor.handle(being1, being2);
            }
          }
        }
       
      }
     
      resolveGroupQueues();
     
      // perform updates
      unresolvedUpdates = updateHelper(unresolvedUpdates);
     
    }
   
    // make sure there's nothing list in the needsMoreSamples lists
    for(Iterator<Interaction> iter = _interactions.iterator(); iter.hasNext(); ) {
      Interaction interaction = iter.next();
      // we only do an interaction if we need to
      if(interaction.getA().hasNeedsMoreSamples())
        interaction.getA().clearNeedsMoreSamples();
      if(interaction.getB().hasNeedsMoreSamples())
        interaction.getB().clearNeedsMoreSamples();
    }
   
    long elapsed = System.currentTimeMillis() - time;
    if(elapsed < _updateLength) {
      try {
        sleep(_updateLength - elapsed);
      } catch (InterruptedException e) {}
    }

  }
 
 

  private List<Being> updateHelper(List<Being> beings) {
    LinkedList<Being> unresolvedUpdates = new LinkedList<Being>();
    for(Iterator<Being> iter = beings.iterator(); iter.hasNext(); ) {
      // iterate through the beings
      Being being = iter.next();
      // apply the update
      synchronized(being) {
        if(!being.processUpdate()) {
          // if the update is unresolved, add it to the unresolved queue
          unresolvedUpdates.add(being);
        }
      }
    }
    return unresolvedUpdates;
  }
 
  /**
   *  Locks the update rate to happen no more than <code>rate</code> times per second.
   *  Default value is 60Hz.
   */
  public void lockUpdateRate(int rate) {
    assert rate > 0 : "World.lockUpdateRate: rate must be greater than zero";
   
    _updateLength = (long)(1.0f / ((float)rate) * 1000.0f);
  }

  /**
   *  Unlocks the update rate (the rate is set to 60Hz by default). Being velocities are
   *  generally constant regardless of the update rate, BUT if the update can be calculated
   *  quickly enough, objects may freeze because the update is faster than the smallest
   *  unit of time the computer can record. Use with caution.
   */
  public void unlockUpdateRate() {
    _updateLength = 0;
  }
 
  public void draw() {
    if(_active) {
      _camera.draw();
    }
  }
 
  public PostOffice getPostOffice() {
    return _postOffice;
  }
 
 
}

 
TOP

Related Classes of hermes.World

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.