/*
* 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.marshalchen.common.uimodule.rebound;
import java.util.*;
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 SpringClock mClock;
private final SpringLooper mSpringLooper;
private long mLastTimeMillis = -1;
private ReentrantCallback<SpringSystemListener> mListeners = new ReentrantCallback<SpringSystemListener>();
private boolean mIdle = true;
/**
* create a new BaseSpringSystem
* @param clock parameterized Clock to allow testability of the physics loop
* @param springLooper parameterized springLooper to allow testability of the
* physics loop
*/
public BaseSpringSystem(SpringClock clock, SpringLooper springLooper) {
if (clock == null) {
throw new IllegalArgumentException("clock is required");
}
if (springLooper == null) {
throw new IllegalArgumentException("springLooper is required");
}
mClock = clock;
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 time system time millis
* @param deltaTime delta since last update in millis
*/
void advance(long time, long deltaTime) {
for (Spring spring : mActiveSprings) {
// advance time in seconds
if (spring.systemShouldAdvance()) {
spring.advance(time / 1000.0, deltaTime / 1000.0);
} else {
mActiveSprings.remove(spring);
}
}
}
/**
* loop the system until idle
*/
public void loop() {
long currentTimeMillis = mClock.now();
if (mLastTimeMillis == -1) {
mLastTimeMillis = currentTimeMillis - 1;
}
long ellapsedMillis = currentTimeMillis - mLastTimeMillis;
mLastTimeMillis = currentTimeMillis;
for (SpringSystemListener listener : mListeners) {
listener.onBeforeIntegrate(this);
}
advance(currentTimeMillis, ellapsedMillis);
synchronized (this) {
if (mActiveSprings.isEmpty()) {
mIdle = true;
mLastTimeMillis = -1;
}
}
for (SpringSystemListener listener : mListeners) {
listener.onAfterIntegrate(this);
}
if (mIdle) {
mSpringLooper.stop();
}
}
/**
* This is used internally by the {@link Spring}s created by this {@link com.marshalchen.common.uimodule.rebound.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");
}
synchronized (this) {
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.addListener(newListener);
}
public void removeListener(SpringSystemListener listenerToRemove) {
if (listenerToRemove == null) {
throw new IllegalArgumentException("listenerToRemove is required");
}
mListeners.removeListener(listenerToRemove);
}
public void removeAllListeners() {
mListeners.clear();
}
}