/*
* Copyright (c) 2013, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
package com.facebook.rebound;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* BaseSpringSystem maintains the set of springs within an Application context. It is responsible for
* Running the spring integration loop and maintains a registry of all the Springs it solves for.
* In addition to listening to physics events on the individual Springs in the system, listeners
* can be added to the BaseSpringSystem itself to provide pre and post integration setup.
*/
public class BaseSpringSystem {
private final Map<String, Spring> mSpringRegistry = new HashMap<String, Spring>();
private final Set<Spring> mActiveSprings = new CopyOnWriteArraySet<Spring>();
private final SpringLooper mSpringLooper;
private final CopyOnWriteArraySet<SpringSystemListener> mListeners = new CopyOnWriteArraySet<SpringSystemListener>();
private boolean mIdle = true;
/**
* create a new BaseSpringSystem
* @param springLooper parameterized springLooper to allow testability of the
* physics loop
*/
public BaseSpringSystem(SpringLooper springLooper) {
if (springLooper == null) {
throw new IllegalArgumentException("springLooper is required");
}
mSpringLooper = springLooper;
mSpringLooper.setSpringSystem(this);
}
/**
* check if the system is idle
* @return is the system idle
*/
public boolean getIsIdle() {
return mIdle;
}
/**
* create a spring with a random uuid for its name.
* @return the spring
*/
public Spring createSpring() {
Spring spring = new Spring(this);
registerSpring(spring);
return spring;
}
/**
* get a spring by name
* @param id id of the spring to retrieve
* @return Spring with the specified key
*/
public Spring getSpringById(String id) {
if (id == null) {
throw new IllegalArgumentException("id is required");
}
return mSpringRegistry.get(id);
}
/**
* return all the springs in the simulator
* @return all the springs
*/
public List<Spring> getAllSprings() {
Collection<Spring> collection = mSpringRegistry.values();
List<Spring> list;
if (collection instanceof List) {
list = (List<Spring>)collection;
} else {
list = new ArrayList<Spring>(collection);
}
return Collections.unmodifiableList(list);
}
/**
* Registers a Spring to this BaseSpringSystem so it can be iterated if active.
* @param spring the Spring to register
*/
void registerSpring(Spring spring) {
if (spring == null) {
throw new IllegalArgumentException("spring is required");
}
if (mSpringRegistry.containsKey(spring.getId())) {
throw new IllegalArgumentException("spring is already registered"); }
mSpringRegistry.put(spring.getId(), spring);
}
/**
* Deregisters a Spring from this BaseSpringSystem, so it won't be iterated anymore. The Spring should
* not be used anymore after doing this.
*
* @param spring the Spring to deregister
*/
void deregisterSpring(Spring spring) {
if (spring == null) {
throw new IllegalArgumentException("spring is required");
}
mActiveSprings.remove(spring);
mSpringRegistry.remove(spring.getId());
}
/**
* update the springs in the system
* @param deltaTime delta since last update in millis
*/
void advance(double deltaTime) {
for (Spring spring : mActiveSprings) {
// advance time in seconds
if (spring.systemShouldAdvance()) {
spring.advance(deltaTime / 1000.0);
} else {
mActiveSprings.remove(spring);
}
}
}
/**
* loop the system until idle
*/
public void loop(double ellapsedMillis) {
for (SpringSystemListener listener : mListeners) {
listener.onBeforeIntegrate(this);
}
advance(ellapsedMillis);
if (mActiveSprings.isEmpty()) {
mIdle = true;
}
for (SpringSystemListener listener : mListeners) {
listener.onAfterIntegrate(this);
}
if (mIdle) {
mSpringLooper.stop();
}
}
/**
* This is used internally by the {@link Spring}s created by this {@link BaseSpringSystem} to notify
* it has reached a state where it needs to be iterated. This will add the spring to the list of
* active springs on this system and start the iteration if the system was idle before this call.
* @param springId the id of the Spring to be activated
*/
void activateSpring(String springId) {
Spring spring = mSpringRegistry.get(springId);
if (spring == null) {
throw new IllegalArgumentException("springId " + springId + " does not reference a registered spring");
}
mActiveSprings.add(spring);
if (getIsIdle()) {
mIdle = false;
mSpringLooper.start();
}
}
/** listeners **/
public void addListener(SpringSystemListener newListener) {
if (newListener == null) {
throw new IllegalArgumentException("newListener is required");
}
mListeners.add(newListener);
}
public void removeListener(SpringSystemListener listenerToRemove) {
if (listenerToRemove == null) {
throw new IllegalArgumentException("listenerToRemove is required");
}
mListeners.remove(listenerToRemove);
}
public void removeAllListeners() {
mListeners.clear();
}
}