text:
package edu.mit.streamjit.impl.compiler2;

import com.google.common.base.Function;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Primitives;
import com.google.common.reflect.TypeResolver;
import com.google.common.reflect.TypeToken;
import edu.mit.streamjit.api.DuplicateSplitter;
import edu.mit.streamjit.api.IllegalStreamGraphException;
import edu.mit.streamjit.api.Input;
import edu.mit.streamjit.api.Joiner;
import edu.mit.streamjit.api.Output;
import edu.mit.streamjit.api.RoundrobinJoiner;
import edu.mit.streamjit.api.RoundrobinSplitter;
import edu.mit.streamjit.api.Splitter;
import edu.mit.streamjit.api.StreamCompilationFailedException;
import edu.mit.streamjit.api.StreamCompiler;
import edu.mit.streamjit.api.WeightedRoundrobinJoiner;
import edu.mit.streamjit.api.WeightedRoundrobinSplitter;
import edu.mit.streamjit.api.Worker;
import edu.mit.streamjit.impl.blob.Blob;
import edu.mit.streamjit.impl.blob.Blob.Token;
import edu.mit.streamjit.impl.blob.Buffer;
import edu.mit.streamjit.impl.blob.DrainData;
import edu.mit.streamjit.impl.blob.PeekableBuffer;
import edu.mit.streamjit.impl.common.Configuration;
import edu.mit.streamjit.impl.common.Configuration.IntParameter;
import edu.mit.streamjit.impl.common.Configuration.SwitchParameter;
import edu.mit.streamjit.impl.common.InputBufferFactory;
import edu.mit.streamjit.impl.common.OutputBufferFactory;
import edu.mit.streamjit.impl.common.Workers;
import edu.mit.streamjit.impl.compiler.Schedule;
import edu.mit.streamjit.impl.compiler2.Compiler2BlobHost.DrainInstruction;
import edu.mit.streamjit.impl.compiler2.Compiler2BlobHost.ReadInstruction;
import edu.mit.streamjit.impl.compiler2.Compiler2BlobHost.WriteInstruction;
import edu.mit.streamjit.test.Benchmark;
import edu.mit.streamjit.test.Benchmarker;
import edu.mit.streamjit.test.apps.fmradio.FMRadio;
import edu.mit.streamjit.util.CollectionUtils;
import edu.mit.streamjit.util.GeneralBinarySearch;
import edu.mit.streamjit.util.bytecode.methodhandles.Combinators;
import edu.mit.streamjit.util.Pair;
import edu.mit.streamjit.util.ReflectionUtils;
import edu.mit.streamjit.util.bytecode.Module;
import edu.mit.streamjit.util.bytecode.ModuleClassLoader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 *
 * @author Jeffrey Bosboom <jbosboom@csail.mit.edu>
 * @since 9/22/2013
 */
public class Compiler2 {
	public static final ImmutableSet<Class<?>> REMOVABLE_WORKERS = ImmutableSet.<Class<?>>of(
			RoundrobinSplitter.class, WeightedRoundrobinSplitter.class, DuplicateSplitter.class,
			RoundrobinJoiner.class, WeightedRoundrobinJoiner.class);
	public static final ImmutableSet<IndexFunctionTransformer> INDEX_FUNCTION_TRANSFORMERS = ImmutableSet.<IndexFunctionTransformer>of(
			new IdentityIndexFunctionTransformer()
//			new ArrayifyIndexFunctionTransformer(false),
//			new ArrayifyIndexFunctionTransformer(true)
	);
	public static final RemovalStrategy REMOVAL_STRATEGY = new BitsetRemovalStrategy();
	public static final FusionStrategy FUSION_STRATEGY = new BitsetFusionStrategy();
	public static final UnboxingStrategy UNBOXING_STRATEGY = new BitsetUnboxingStrategy();
	public static final AllocationStrategy ALLOCATION_STRATEGY = new SubsetBiasAllocationStrategy(8);
	public static final StorageStrategy INTERNAL_STORAGE_STRATEGY = new TuneInternalStorageStrategy();
	public static final StorageStrategy EXTERNAL_STORAGE_STRATEGY = new TuneExternalStorageStrategy();
	public static final SwitchingStrategy SWITCHING_STRATEGY = SwitchingStrategy.tunePerWorker();
	private static final AtomicInteger PACKAGE_NUMBER = new AtomicInteger();
	private final ImmutableSet<Worker<?, ?>> workers;
	private final ImmutableSet<ActorArchetype> archetypes;
	private final NavigableSet<Actor> actors;
	private ImmutableSortedSet<ActorGroup> groups;
	private ImmutableSortedSet<WorkerActor> actorsToBeRemoved;
	private final Configuration config;
	private final int maxNumCores;
	private final DrainData initialState;
	/**
	 * If the blob is the entire graph, this is the overall input; else null.
	 */
	private final Input<?> overallInput;
	/**
	 * If the blob is the entire graph, this is the overall output; else null.
	 */
	private final Output<?> overallOutput;
	private Buffer overallInputBuffer, overallOutputBuffer;
	private ImmutableMap<Token, Buffer> precreatedBuffers;
	private final ImmutableMap<Token, ImmutableList<Object>> initialStateDataMap;
	private final Set<Storage> storage;
	private ImmutableMap<ActorGroup, Integer> externalSchedule;
	private final Module module = new Module();
	private final ModuleClassLoader classloader = new ModuleClassLoader(module);
	private final String packageName = "compiler"+PACKAGE_NUMBER.getAndIncrement();
	private ImmutableMap<ActorGroup, Integer> initSchedule;
	/**
	 * For each token in the blob, the number of items live on that edge after
	 * the init schedule, without regard to removals.  (We could recover this
	 * information from Actor.inputSlots when we're creating drain instructions,
	 * but storing it simplifies the code and permits asserting we didn't lose
	 * any items.)
	 */
	private ImmutableMap<Token, Integer> postInitLiveness;
	/**
	 * ConcreteStorage instances used during initialization (bound into the
	 * initialization code).
	 */
	private ImmutableMap<Storage, ConcreteStorage> initStorage;
	/**
	 * ConcreteStorage instances used during the steady-state (bound into the
	 * steady-state code).
	 */
	private ImmutableMap<Storage, ConcreteStorage> steadyStateStorage;
	/**
	 * Code to run the initialization schedule.  (Initialization is
	 * single-threaded.)
	 */
	private MethodHandle initCode;
	/**
	 * Code to run the steady state schedule.  The blob host takes care of
	 * filling/flushing buffers, adjusting storage and the global barrier.
	 */
	private ImmutableList<MethodHandle> steadyStateCode;
	private final List<ReadInstruction> initReadInstructions = new ArrayList<>();
	private final List<WriteInstruction> initWriteInstructions = new ArrayList<>();
	private final List<Runnable> migrationInstructions = new ArrayList<>();
	private final List<ReadInstruction> readInstructions = new ArrayList<>();
	private final List<WriteInstruction> writeInstructions = new ArrayList<>();
	private final List<DrainInstruction> drainInstructions = new ArrayList<>();
	public Compiler2(Set<Worker<?, ?>> workers, Configuration config, int maxNumCores, DrainData initialState, Input<?> input, Output<?> output) {
		this.workers = ImmutableSet.copyOf(workers);
		Map<Class<?>, ActorArchetype> archetypesBuilder = new HashMap<>();
		Map<Worker<?, ?>, WorkerActor> workerActors = new HashMap<>();
		for (Worker<?, ?> w : workers) {
			@SuppressWarnings("unchecked")
			Class<? extends Worker<?, ?>> wClass = (Class<? extends Worker<?, ?>>)w.getClass();
			if (archetypesBuilder.get(wClass) == null)
				archetypesBuilder.put(wClass, new ActorArchetype(wClass, module));
			WorkerActor actor = new WorkerActor(w, archetypesBuilder.get(wClass));
			workerActors.put(w, actor);
		}
		this.archetypes = ImmutableSet.copyOf(archetypesBuilder.values());

		Map<Token, TokenActor> tokenActors = new HashMap<>();
		Table<Actor, Actor, Storage> storageTable = HashBasedTable.create();
		int[] inputTokenId = new int[]{Integer.MIN_VALUE}, outputTokenId = new int[]{Integer.MAX_VALUE};
		for (WorkerActor a : workerActors.values())
			a.connect(ImmutableMap.copyOf(workerActors), tokenActors, storageTable, inputTokenId, outputTokenId);
		this.actors = new TreeSet<>();
		this.actors.addAll(workerActors.values());
		this.actors.addAll(tokenActors.values());
		this.storage = new HashSet<>(storageTable.values());

		this.config = config;
		this.maxNumCores = maxNumCores;
		this.initialState = initialState;
		ImmutableMap.Builder<Token, ImmutableList<Object>> initialStateDataMapBuilder = ImmutableMap.builder();
		if (initialState != null) {
			for (Table.Cell<Actor, Actor, Storage> cell : storageTable.cellSet()) {
				Token tok;
				if (cell.getRowKey() instanceof TokenActor)
					tok = ((TokenActor)cell.getRowKey()).token();
				else if (cell.getColumnKey() instanceof TokenActor)
					tok = ((TokenActor)cell.getColumnKey()).token();
				else
					tok = new Token(((WorkerActor)cell.getRowKey()).worker(),
							((WorkerActor)cell.getColumnKey()).worker());
				ImmutableList<Object> data = initialState.getData(tok);
				if (data != null && !data.isEmpty()) {
					initialStateDataMapBuilder.put(tok, data);
					cell.getValue().initialData().add(Pair.make(data, IndexFunction.identity()));
				}
			}
		}
		this.initialStateDataMap = initialStateDataMapBuilder.build();
		this.overallInput = input;
		this.overallOutput = output;
	}

	public Blob compile() {
		findRemovals();
		fuse();
		schedule();

//		identityRemoval();
		splitterRemoval();
		joinerRemoval();

		inferTypes();
		unbox();

		generateArchetypalCode();
		createBuffers();
		createInitCode();
		createSteadyStateCode();
		return instantiateBlob();
	}

	private void findRemovals() {
		ImmutableSortedSet.Builder<WorkerActor> builder = ImmutableSortedSet.naturalOrder();
		next_worker: for (WorkerActor a : Iterables.filter(actors, WorkerActor.class)) {
			if (!REMOVABLE_WORKERS.contains(a.worker().getClass())) continue;
			for (Storage s : a.outputs())
				if (!s.initialData().isEmpty())
					continue next_worker;
			for (Storage s : a.inputs())
				if (!s.initialData().isEmpty())
					continue next_worker;
			if (!REMOVAL_STRATEGY.remove(a, config)) continue;
			builder.add(a);
		}
		this.actorsToBeRemoved = builder.build();
	}

	/**
	 * Fuses actors into groups as directed by the configuration.
	 */
	private void fuse() {
		List<ActorGroup> actorGroups = new ArrayList<>();
		for (Actor a : actors)
			actorGroups.add(ActorGroup.of(a));

		//Fuse as much as possible.
		just_fused: do {
			try_fuse: for (Iterator<ActorGroup> it = actorGroups.iterator(); it.hasNext();) {
				ActorGroup g = it.next();
				if (g.isTokenGroup())
					continue try_fuse;
				for (ActorGroup pg : g.predecessorGroups())
					if (pg.isTokenGroup())
						continue try_fuse;
				if (g.isPeeking() || g.predecessorGroups().size() > 1)
					continue try_fuse;
				for (Storage s : g.inputs())
					if (!s.initialData().isEmpty())
						continue try_fuse;

				//We are assuming FusionStrategies are all happy to work
				//group-by-group.  If later we want to make all decisions at
				//once, we'll refactor existing FusionStrategies to inherit from
				//a base class containing this loop.
				if (!FUSION_STRATEGY.fuseUpward(g, config))
					continue try_fuse;

				ActorGroup gpred = Iterables.getOnlyElement(g.predecessorGroups());
				ActorGroup fusedGroup = ActorGroup.fuse(g, gpred);
				it.remove();
				actorGroups.remove(gpred);
				actorGroups.add(fusedGroup);
				continue just_fused;
			}
			break;
		} while (true);

		this.groups = ImmutableSortedSet.copyOf(actorGroups);

		Boolean reportFusion = (Boolean)config.getExtraData("reportFusion");
		if (reportFusion != null && reportFusion) {
			for (ActorGroup g : groups) {
				if (g.isTokenGroup()) continue;
				List<Integer> list = new ArrayList<>();
				for (Actor a : g.actors())
					list.add(a.id());
				System.out.println(com.google.common.base.Joiner.on(' ').join(list));
			}
			System.out.flush();
			System.exit(0);
		}
	}

	/**
	 * Computes each group's internal schedule and the external schedule.
	 */
	private void schedule() {
		for (ActorGroup g : groups)
			internalSchedule(g);
		externalSchedule();
		initSchedule();
	}

	private void externalSchedule() {
		Schedule.Builder<ActorGroup> scheduleBuilder = Schedule.builder();
		scheduleBuilder.addAll(groups);
		for (ActorGroup g : groups) {
			for (Storage e : g.outputs()) {
				Actor upstream = Iterables.getOnlyElement(e.upstream());
				Actor downstream = Iterables.getOnlyElement(e.downstream());
				ActorGroup other = downstream.group();
				int upstreamAdjust = g.schedule().get(upstream);
				int downstreamAdjust = other.schedule().get(downstream);
				scheduleBuilder.connect(g, other)
						.push(e.push() * upstreamAdjust)
						.pop(e.pop() * downstreamAdjust)
						.peek(e.peek() * downstreamAdjust)
						.bufferExactly(0);
			}
		}
		int multiplier = config.getParameter("multiplier", IntParameter.class).getValue();
		scheduleBuilder.multiply(multiplier);
		try {
			externalSchedule = scheduleBuilder.build().getSchedule();
		} catch (Schedule.ScheduleException ex) {
			throw new StreamCompilationFailedException("couldn't find external schedule; mult = "+multiplier, ex);
		}
	}

	/**
	 * Computes the internal schedule for the given group.
	 */
	private void internalSchedule(ActorGroup g) {
		Schedule.Builder<Actor> scheduleBuilder = Schedule.builder();
		scheduleBuilder.addAll(g.actors());
		for (Actor a : g.actors())
			scheduleBuilder.executeAtLeast(a, 1);
		for (Storage s : g.internalEdges()) {
			scheduleBuilder.connect(Iterables.getOnlyElement(s.upstream()), Iterables.getOnlyElement(s.downstream()))
					.push(s.push())
					.pop(s.pop())
					.peek(s.peek())
					.bufferExactly(0);
		}

		try {
			Schedule<Actor> schedule = scheduleBuilder.build();
			g.setSchedule(schedule.getSchedule());
		} catch (Schedule.ScheduleException ex) {
			throw new StreamCompilationFailedException("couldn't find internal schedule for group "+g+"\n"+scheduleBuilder.toString(), ex);
		}
	}

	private void initSchedule() {
		Schedule.Builder<ActorGroup> scheduleBuilder = Schedule.builder();
		scheduleBuilder.addAll(groups);
		for (Storage s : storage) {
			if (s.isInternal()) continue;
			Actor upstream = Iterables.getOnlyElement(s.upstream()), downstream = Iterables.getOnlyElement(s.downstream());
			int upstreamAdjust = upstream.group().schedule().get(upstream);
			int downstreamAdjust = downstream.group().schedule().get(downstream);
			int throughput, excessPeeks;
			//TODO: avoid double-buffering token groups here?
			if (actorsToBeRemoved.contains(downstream) && false)
				throughput = excessPeeks = 0;
			else {
				throughput = s.push() * upstreamAdjust * externalSchedule.get(upstream.group());
				excessPeeks = Math.max(s.peek() - s.pop(), 0);
			}
			int initialDataSize = Iterables.getOnlyElement(s.initialData(), new Pair<>(ImmutableList.<Object>of(), (MethodHandle)null)).first.size();
			scheduleBuilder.connect(upstream.group(), downstream.group())
					.push(s.push() * upstreamAdjust)
					.pop(s.pop() * downstreamAdjust)
					.peek(s.peek() * downstreamAdjust)
					.bufferAtLeast(throughput + excessPeeks - initialDataSize);
		}

		IntParameter initBufferingCostParam = config.getParameter("InitBufferingCost", IntParameter.class);
		int initBufferCost = initBufferingCostParam.getValue(), fireCost = initBufferingCostParam.getMax() - initBufferCost;
		scheduleBuilder.costs(fireCost, initBufferCost);
		try {
			Schedule<ActorGroup> schedule = scheduleBuilder.build();
			this.initSchedule = schedule.getSchedule();
		} catch (Schedule.ScheduleException ex) {
			throw new StreamCompilationFailedException("couldn't find init schedule", ex);
		}

		ImmutableMap.Builder<Token, Integer> postInitLivenessBuilder = ImmutableMap.builder();
		for (Storage s : storage) {
			if (s.isInternal()) continue;
			Actor upstream = Iterables.getOnlyElement(s.upstream()), downstream = Iterables.getOnlyElement(s.downstream());
			int upstreamExecutions = upstream.group().schedule().get(upstream) * initSchedule.get(upstream.group());
			int downstreamExecutions = downstream.group().schedule().get(downstream) * initSchedule.get(downstream.group());
			int liveItems = s.push() * upstreamExecutions - s.pop() * downstreamExecutions + s.initialDataIndices().size();
			assert liveItems >= 0 : s;

			int index = downstream.inputs().indexOf(s);
			assert index != -1;
			Token token;
			if (downstream instanceof WorkerActor) {
				Worker<?, ?> w = ((WorkerActor)downstream).worker();
				token = downstream.id() == 0 ? Token.createOverallInputToken(w) :
						new Token(Workers.getPredecessors(w).get(index), w);
			} else
				token = ((TokenActor)downstream).token();
			StorageSlotList inputSlots = downstream.inputSlots(index);
			inputSlots.ensureCapacity(liveItems);
			for (int i = 0; i < liveItems; ++i)
				inputSlots.add(token, i);
			postInitLivenessBuilder.put(token, liveItems);
		}
		this.postInitLiveness = postInitLivenessBuilder.build();

		int initScheduleSize = 0;
		for (int i : initSchedule.values())
			initScheduleSize += i;
//		System.out.println("init schedule size "+initScheduleSize);
		int totalBuffering = 0;
		for (int i : postInitLiveness.values())
			totalBuffering += i;
//		System.out.println("total items buffered "+totalBuffering);
	}

	private void splitterRemoval() {
		for (WorkerActor splitter : actorsToBeRemoved) {
			if (!(splitter.worker() instanceof Splitter)) continue;
			List<IndexFunction> transfers = splitterTransferFunctions(splitter);
			Storage survivor = Iterables.getOnlyElement(splitter.inputs());
			//Remove all instances of splitter, not just the first.
			survivor.downstream().removeAll(ImmutableList.of(splitter));
			IndexFunction Sin = Iterables.getOnlyElement(splitter.inputIndexFunctions());
			StorageSlotList drainInfo = splitter.inputSlots(0);
			for (int i = 0; i < splitter.outputs().size(); ++i) {
				Storage victim = splitter.outputs().get(i);
				IndexFunction t = transfers.get(i);
				for (Actor a : victim.downstream()) {
					List<Storage> inputs = a.inputs();
					List<IndexFunction> inputIndices = a.inputIndexFunctions();
					for (int j = 0; j < inputs.size(); ++j) {
						StorageSlotList inputSlots = a.inputSlots(j);
						if (inputs.get(j).equals(victim)) {
							inputs.set(j, survivor);
							survivor.downstream().add(a);
							inputIndices.set(j, inputIndices.get(j).andThen(t));
							if (splitter.push(i) > 0) {
								IndexFunction idxFxn = a.inputIndexFunctions().get(j);
								int bulkSize = GeneralBinarySearch.binarySearch(idx -> idxFxn.applyAsInt(idx) < drainInfo.size(), 0);
								int[] bulk = getBulk(bulkSize);
								idxFxn.applyBulk(bulk);
								inputSlots.ensureCapacity(bulkSize);
								for (int q : bulk) {
									inputSlots.add(drainInfo.get(q));
									drainInfo.setDuplicate(q);
								}
							}
							inputIndices.set(j, inputIndices.get(j).andThen(Sin));
						}
					}
				}

				for (Pair<ImmutableList<Object>, IndexFunction> item : victim.initialData())
					survivor.initialData().add(new Pair<>(item.first, item.second.andThen(t)));
				storage.remove(victim);
			}

			removeActor(splitter);
			assert consistency();
		}
	}

	/**
	 * Returns transfer functions for the given splitter.
	 *
	 * A splitter has one transfer function for each output that maps logical
	 * output indices to logical input indices (representing the splitter's
	 * distribution pattern).
	 * @param a an actor
	 * @return transfer functions, or null
	 */
	private List<IndexFunction> splitterTransferFunctions(WorkerActor a) {
		assert REMOVABLE_WORKERS.contains(a.worker().getClass()) : a.worker().getClass();
		if (a.worker() instanceof RoundrobinSplitter || a.worker() instanceof WeightedRoundrobinSplitter) {
			int[] weights = new int[a.outputs().size()];
			for (int i = 0; i < weights.length; ++i)
				weights[i] = a.push(i);
			return roundrobinTransferFunctions(weights);
		} else if (a.worker() instanceof DuplicateSplitter) {
			return Collections.nCopies(a.outputs().size(), IndexFunction.identity());
		} else
			throw new AssertionError();
	}

	private void joinerRemoval() {
		for (WorkerActor joiner : actorsToBeRemoved) {
			if (!(joiner.worker() instanceof Joiner)) continue;
			List<IndexFunction> transfers = joinerTransferFunctions(joiner);
			Storage survivor = Iterables.getOnlyElement(joiner.outputs());
			//Remove all instances of joiner, not just the first.
			survivor.upstream().removeAll(ImmutableList.of(joiner));
			IndexFunction Jout = Iterables.getOnlyElement(joiner.outputIndexFunctions());
			for (int i = 0; i < joiner.inputs().size(); ++i) {
				Storage victim = joiner.inputs().get(i);
				IndexFunction t = transfers.get(i);
				IndexFunction t2 = t.andThen(Jout);
				for (Actor a : victim.upstream()) {
					List<Storage> outputs = a.outputs();
					List<IndexFunction> outputIndices = a.outputIndexFunctions();
					for (int j = 0; j < outputs.size(); ++j)
						if (outputs.get(j).equals(victim)) {
							outputs.set(j, survivor);
							outputIndices.set(j, outputIndices.get(j).andThen(t2));
							survivor.upstream().add(a);
						}
				}

				for (Pair<ImmutableList<Object>, IndexFunction> item : victim.initialData())
					survivor.initialData().add(new Pair<>(item.first,item.second.andThen(t2)));
				storage.remove(victim);
			}

			//Linearize drain info from the joiner's inputs.
			int maxIdx = 0;
			for (int i = 0; i < joiner.inputs().size(); ++i) {
				IndexFunction t = transfers.get(i);
				StorageSlotList inputSlots = joiner.inputSlots(i);
				if (inputSlots.size() > 0)
					maxIdx = Math.max(maxIdx, t.applyAsInt(inputSlots.size()-1));
			}
			StorageSlotList linearizedInput = StorageSlotList.ofHoles(maxIdx+1);
			for (int i = 0; i < joiner.inputs().size(); ++i) {
				IndexFunction t = transfers.get(i);
				StorageSlotList inputSlots = joiner.inputSlots(i);
				int[] bulk = getBulk(inputSlots.size());
				t.applyBulk(bulk);
				for (int idx = 0; idx < inputSlots.size(); ++idx)
					linearizedInput.set(bulk[idx], inputSlots.get(idx));
				inputSlots.clear();
				inputSlots.trimToSize();
			}

			if (!linearizedInput.isEmpty()) {
				for (Actor a : survivor.downstream())
					for (int j = 0; j < a.inputs().size(); ++j)
						if (a.inputs().get(j).equals(survivor)) {
							StorageSlotList inputSlots = a.inputSlots(j);
							IndexFunction idxFxn = a.inputIndexFunctions().get(j);
							int bulkSize = GeneralBinarySearch.binarySearch(idx -> idxFxn.applyAsInt(idx) < linearizedInput.size(), 0);
							int[] bulk = getBulk(bulkSize);
							idxFxn.applyBulk(bulk);
							for (int q : bulk) {
								StorageSlot slot = linearizedInput.get(q);
								inputSlots.add(slot);
								linearizedInput.setDuplicate(q);
							}
						}
			}

//			System.out.println("removed "+joiner);
			removeActor(joiner);
			assert consistency();
		}
	}

	private List<IndexFunction> joinerTransferFunctions(WorkerActor a) {
		assert REMOVABLE_WORKERS.contains(a.worker().getClass()) : a.worker().getClass();
		if (a.worker() instanceof RoundrobinJoiner || a.worker() instanceof WeightedRoundrobinJoiner) {
			int[] weights = new int[a.inputs().size()];
			for (int i = 0; i < weights.length; ++i)
				weights[i] = a.pop(i);
			return roundrobinTransferFunctions(weights);
		} else
			throw new AssertionError();
	}

	private List<IndexFunction> roundrobinTransferFunctions(int[] weights) {
		int[] weightPrefixSum = new int[weights.length + 1];
		for (int i = 1; i < weightPrefixSum.length; ++i)
			weightPrefixSum[i] = weightPrefixSum[i-1] + weights[i-1];
		int N = weightPrefixSum[weightPrefixSum.length-1];
		//t_x(i) = N(i/w[x]) + sum_0_x-1{w} + (i mod w[x])
		//where the first two terms select a "window" and the third is the
		//index into that window.
		ImmutableList.Builder<IndexFunction> transfer = ImmutableList.builder();
		for (int x = 0; x < weights.length; ++x) {
			//don't capture the array in the lambda
			int weight = weights[x], prefixSum = weightPrefixSum[x];
			transfer.add(new RoundrobinTransferIndexFunction(weight, prefixSum, N));
		}
		return transfer.build();
	}
	private static int _roundrobinTransferFunction(int weight, int prefixSum, int N, int i) {
		//assumes nonnegative indices
		return N*(i/weight) + prefixSum + (i % weight);
	}
	private static final class RoundrobinTransferIndexFunction implements IndexFunction {
		private final int weight, prefixSum, N;
		private RoundrobinTransferIndexFunction(int weight, int prefixSum, int N) {
			this.weight = weight;
			this.prefixSum = prefixSum;
			this.N = N;
		}
		@Override
		public int applyAsInt(int operand) {
			return _roundrobinTransferFunction(weight, prefixSum, N, operand);
		}
		@Override
		public void applyBulk(int[] bulk) {
			//TODO: is just a specialization point enough?
			for (int i = 0; i < bulk.length; ++i)
//				bulk[i] = applyAsInt(bulk[i]);
				bulk[i] = _roundrobinTransferFunction(weight, prefixSum, N, bulk[i]);
		}
	}

	/**
	 * Removes an Actor from this compiler's data structures.  The Actor should
	 * already have been unlinked from the graph (no incoming edges); this takes
	 * care of removing it from the actors set, its actor group (possibly
	 * removing the group if it's now empty), and the schedule.
	 * @param a the actor to remove
	 */
	private void removeActor(Actor a) {
		assert actors.contains(a) : a;
		actors.remove(a);
		ActorGroup g = a.group();
		g.remove(a);
		if (g.actors().isEmpty()) {
			groups = ImmutableSortedSet.copyOf(Sets.difference(groups, ImmutableSet.of(g)));
			externalSchedule = ImmutableMap.copyOf(Maps.difference(externalSchedule, ImmutableMap.of(g, 0)).entriesOnlyOnLeft());
			initSchedule = ImmutableMap.copyOf(Maps.difference(initSchedule, ImmutableMap.of(g, 0)).entriesOnlyOnLeft());
		}
	}

	private boolean consistency() {
		Set<Storage> usedStorage = new HashSet<>();
		for (Actor a : actors) {
			usedStorage.addAll(a.inputs());
			usedStorage.addAll(a.outputs());
		}
		if (!storage.equals(usedStorage)) {
			Set<Storage> unused = Sets.difference(storage, usedStorage);
			Set<Storage> untracked = Sets.difference(usedStorage, storage);
			throw new AssertionError(String.format("inconsistent storage:%n\tunused: %s%n\tuntracked:%s%n", unused, untracked));
		}
		return true;
	}

	//<editor-fold defaultstate="collapsed" desc="Unimplemented optimization stuff">
//	/**
//	 * Removes Identity instances from the graph, unless doing so would make the
//	 * graph empty.
//	 */
//	private void identityRemoval() {
//		//TODO: remove from group, possibly removing the group if it becomes empty
//		for (Iterator<Actor> iter = actors.iterator(); iter.hasNext();) {
//			if (actors.size() == 1)
//				break;
//			Actor actor = iter.next();
//			if (!actor.archetype().workerClass().equals(Identity.class))
//				continue;
//
//			iter.remove();
//			assert actor.predecessors().size() == 1 && actor.successors().size() == 1;
//			Object upstream = actor.predecessors().get(0), downstream = actor.successors().get(0);
//			if (upstream instanceof Actor)
//				replace(((Actor)upstream).successors(), actor, downstream);
//			if (downstream instanceof Actor)
//				replace(((Actor)downstream).predecessors(), actor, upstream);
//			//No index function changes required for Identity actors.
//		}
//	}
//
//	private static int replace(List<Object> list, Object target, Object replacement) {
//		int replacements = 0;
//		for (int i = 0; i < list.size(); ++i)
//			if (Objects.equals(list.get(0), target)) {
//				list.set(i, replacement);
//				++replacements;
//			}
//		return replacements;
//	}
	//</editor-fold>

	/**
	 * Performs type inference to replace type variables with concrete types.
	 * For now, we only care about wrapper types.
	 */
	public void inferTypes() {
		while (inferUpward() || inferDownward());
	}

	private boolean inferUpward() {
		boolean changed = false;
		//For each storage, if a reader's input type is a final type, all
		//writers' output types must be that final type.  (Wrappers are final,
		//so this works for wrappers, and maybe detects errors related to other
		//types.)
		for (Storage s : storage) {
			Set<TypeToken<?>> finalInputTypes = new HashSet<>();
			for (Actor a : s.downstream())
				if (Modifier.isFinal(a.inputType().getRawType().getModifiers()))
					finalInputTypes.add(a.inputType());
			if (finalInputTypes.isEmpty()) continue;
			if (finalInputTypes.size() > 1)
				throw new IllegalStreamGraphException("Type mismatch among readers: "+s.downstream());

			TypeToken<?> inputType = finalInputTypes.iterator().next();
			for (Actor a : s.upstream())
				if (!a.outputType().equals(inputType)) {
					TypeToken<?> oldOutputType = a.outputType();
					TypeResolver resolver = new TypeResolver().where(oldOutputType.getType(), inputType.getType());
					TypeToken<?> newOutputType = TypeToken.of(resolver.resolveType(oldOutputType.getType()));
					if (!oldOutputType.equals(newOutputType)) {
						a.setOutputType(newOutputType);
//						System.out.println("inferUpward: inferred "+a+" output type: "+oldOutputType+" -> "+newOutputType);
						changed = true;
					}

					TypeToken<?> oldInputType = a.inputType();
					TypeToken<?> newInputType = TypeToken.of(resolver.resolveType(oldInputType.getType()));
					if (!oldInputType.equals(newInputType)) {
						a.setInputType(newInputType);
//						System.out.println("inferUpward: inferred "+a+" input type: "+oldInputType+" -> "+newInputType);
						changed = true;
					}
				}
		}
		return changed;
	}

	private boolean inferDownward() {
		boolean changed = false;
		//For each storage, find the most specific common type among all the
		//writers' output types, then if it's final, unify with any variable or
		//wildcard reader input type.  (We only unify if final to avoid removing
		//a type variable too soon.  We could also refine concrete types like
		//Object to a more specific subclass.)
		for (Storage s : storage) {
			Set<? extends TypeToken<?>> commonTypes = null;
			for (Actor a : s.upstream())
				if (commonTypes == null)
					commonTypes = a.outputType().getTypes();
				else
					commonTypes = Sets.intersection(commonTypes, a.outputType().getTypes());
			if (commonTypes.isEmpty())
				throw new IllegalStreamGraphException("No common type among writers: "+s.upstream());

			TypeToken<?> mostSpecificType = commonTypes.iterator().next();
			if (!Modifier.isFinal(mostSpecificType.getRawType().getModifiers()))
				continue;
			for (Actor a : s.downstream()) {
				TypeToken<?> oldInputType = a.inputType();
				//TODO: this isn't quite right?
				if (!ReflectionUtils.containsVariableOrWildcard(oldInputType.getType())) continue;

				TypeResolver resolver = new TypeResolver().where(oldInputType.getType(), mostSpecificType.getType());
				TypeToken<?> newInputType = TypeToken.of(resolver.resolveType(oldInputType.getType()));
				if (!oldInputType.equals(newInputType)) {
					a.setInputType(newInputType);
//					System.out.println("inferDownward: inferred "+a+" input type: "+oldInputType+" -> "+newInputType);
					changed = true;
				}

				TypeToken<?> oldOutputType = a.outputType();
				TypeToken<?> newOutputType = TypeToken.of(resolver.resolveType(oldOutputType.getType()));
				if (!oldOutputType.equals(newOutputType)) {
					a.setOutputType(newOutputType);
//					System.out.println("inferDownward: inferred "+a+" output type: "+oldOutputType+" -> "+newOutputType);
					changed = true;
				}
			}
		}
		return changed;
	}

	/**
	 * Unboxes storage types and Actor input and output types.
	 */
	private void unbox() {
		for (Storage s : storage) {
			if (isUnboxable(s.contentType()) && UNBOXING_STRATEGY.unboxStorage(s, config)) {
				TypeToken<?> contents = s.contentType();
				Class<?> type = contents.unwrap().getRawType();
				s.setType(type);
//				if (!s.type().equals(contents.getRawType()))
//					System.out.println("unboxed "+s+" to "+type);
			}
		}

		for (WorkerActor a : Iterables.filter(actors, WorkerActor.class)) {
			if (isUnboxable(a.inputType()) && UNBOXING_STRATEGY.unboxInput(a, config)) {
				TypeToken<?> oldType = a.inputType();
				a.setInputType(oldType.unwrap());
//				if (!a.inputType().equals(oldType))
//					System.out.println("unboxed input of "+a+": "+oldType+" -> "+a.inputType());
			}
			if (isUnboxable(a.outputType()) && UNBOXING_STRATEGY.unboxOutput(a, config)) {
				TypeToken<?> oldType = a.outputType();
				a.setOutputType(oldType.unwrap());
//				if (!a.outputType().equals(oldType))
//					System.out.println("unboxed output of "+a+": "+oldType+" -> "+a.outputType());
			}
		}
	}

	private boolean isUnboxable(TypeToken<?> type) {
		return Primitives.isWrapperType(type.getRawType()) && !type.getRawType().equals(Void.class);
	}

	private void generateArchetypalCode() {
		for (final ActorArchetype archetype : archetypes) {
			Iterable<WorkerActor> workerActors = FluentIterable.from(actors)
					.filter(WorkerActor.class)
					.filter(wa -> wa.archetype().equals(archetype));
			archetype.generateCode(packageName, classloader, workerActors);
			for (WorkerActor wa : workerActors)
				wa.setStateHolder(archetype.makeStateHolder(wa));
		}
	}

	/**
	 * If we're compiling an entire graph, create the overall input and output
	 * buffers now so we can take advantage of
	 * PeekableBuffers/PokeableBuffers.  Otherwise we must pre-init()
	 * our read/write instructions to refer to these buffers, since they won't
	 * be passed to the blob's installBuffers().
	 */
	private void createBuffers() {
		assert (overallInput == null) == (overallOutput == null);
		if (overallInput == null) {
			this.precreatedBuffers = ImmutableMap.of();
			return;
		}

		ActorGroup inputGroup = null, outputGroup = null;
		Token inputToken = null, outputToken = null;
		for (ActorGroup g : groups)
			if (g.isTokenGroup()) {
				assert g.actors().size() == 1;
				TokenActor ta = (TokenActor)g.actors().iterator().next();
				assert g.schedule().get(ta) == 1;
				if (ta.isInput()) {
					assert inputGroup == null;
					inputGroup = g;
					inputToken = ta.token();
				}
				if (ta.isOutput()) {
					assert outputGroup == null;
					outputGroup = g;
					outputToken = ta.token();
				}
			}
		this.overallInputBuffer = InputBufferFactory.unwrap(overallInput).createReadableBuffer(
				Math.max(initSchedule.get(inputGroup), externalSchedule.get(inputGroup)));
		this.overallOutputBuffer = OutputBufferFactory.unwrap(overallOutput).createWritableBuffer(
				Math.max(initSchedule.get(outputGroup), externalSchedule.get(outputGroup)));
		this.precreatedBuffers = ImmutableMap.<Token, Buffer>builder()
				.put(inputToken, overallInputBuffer)
				.put(outputToken, overallOutputBuffer)
				.build();
	}

	private void createInitCode() {
		ImmutableMap<Actor, ImmutableList<IndexFunction>> indexFxnBackup = adjustOutputIndexFunctions(Storage::initialDataIndices);

		this.initStorage = createStorage(false, new PeekPokeStorageFactory(InternalArrayConcreteStorage.initFactory(initSchedule)));
		initReadInstructions.add(new InitDataReadInstruction(initStorage, initialStateDataMap));

		ImmutableMap<Storage, ConcreteStorage> internalStorage = createStorage(true, InternalArrayConcreteStorage.initFactory(initSchedule));
		IndexFunctionTransformer ift = new IdentityIndexFunctionTransformer();
		ImmutableTable.Builder<Actor, Integer, IndexFunctionTransformer> inputTransformers = ImmutableTable.builder(),
				outputTransformers = ImmutableTable.builder();
		for (Actor a : Iterables.filter(actors, WorkerActor.class)) {
			for (int i = 0; i < a.inputs().size(); ++i)
				inputTransformers.put(a, i, ift);
			for (int i = 0; i < a.outputs().size(); ++i)
				outputTransformers.put(a, i, ift);
		}
		ImmutableMap.Builder<ActorGroup, Integer> unrollFactors = ImmutableMap.builder();
		for (ActorGroup g : groups)
			unrollFactors.put(g, 1);

		/**
		 * During init, all (nontoken) groups are assigned to the same Core in
		 * topological order (via the ordering on ActorGroups).  At the same
		 * time we build the token init schedule information required by the
		 * blob host.
		 */
		Core initCore = new Core(CollectionUtils.union(initStorage, internalStorage), (table, wa) -> Combinators.lookupswitch(table), unrollFactors.build(), inputTransformers.build(), outputTransformers.build());
		for (ActorGroup g : groups)
			if (!g.isTokenGroup())
				initCore.allocate(g, Range.closedOpen(0, initSchedule.get(g)));
			else {
				assert g.actors().size() == 1;
				TokenActor ta = (TokenActor)g.actors().iterator().next();
				assert g.schedule().get(ta) == 1;
				ConcreteStorage storage = initStorage.get(Iterables.getOnlyElement(ta.isInput() ? g.outputs() : g.inputs()));
				int executions = initSchedule.get(g);
				if (ta.isInput())
					initReadInstructions.add(makeReadInstruction(ta, storage, executions));
				else
					initWriteInstructions.add(makeWriteInstruction(ta, storage, executions));
			}
		this.initCode = initCore.code();

		restoreOutputIndexFunctions(indexFxnBackup);
	}

	private static final class AdditionIndexFunction implements IndexFunction {
		private final int addend;
		private AdditionIndexFunction(int addend) {
			this.addend = addend;
		}
		@Override
		public int applyAsInt(int operand) {
			return operand + addend;
		}
		@Override
		public void applyBulk(int[] bulk) {
			for (int i = 0; i < bulk.length; ++i)
				bulk[i] += addend;
		}
	}

	private void createSteadyStateCode() {
		for (Actor a : actors) {
			for (int i = 0; i < a.outputs().size(); ++i) {
				Storage s = a.outputs().get(i);
				if (s.isInternal()) continue;
				int itemsWritten = a.push(i) * initSchedule.get(a.group()) * a.group().schedule().get(a);
				a.outputIndexFunctions().set(i, a.outputIndexFunctions().get(i).compose(new AdditionIndexFunction(itemsWritten)));
			}
			for (int i = 0; i < a.inputs().size(); ++i) {
				Storage s = a.inputs().get(i);
				if (s.isInternal()) continue;
				int itemsRead = a.pop(i) * initSchedule.get(a.group()) * a.group().schedule().get(a);
				a.inputIndexFunctions().set(i, a.inputIndexFunctions().get(i).compose(new AdditionIndexFunction(itemsRead)));
			}
		}

		for (Storage s : storage)
			s.computeSteadyStateRequirements(externalSchedule);
		this.steadyStateStorage = createStorage(false, new PeekPokeStorageFactory(EXTERNAL_STORAGE_STRATEGY.asFactory(config)));
		ImmutableMap<Storage, ConcreteStorage> internalStorage = createStorage(true, INTERNAL_STORAGE_STRATEGY.asFactory(config));

		List<Core> ssCores = new ArrayList<>(maxNumCores);
		IndexFunctionTransformer ift = new IdentityIndexFunctionTransformer();
		for (int i = 0; i < maxNumCores; ++i) {
			ImmutableTable.Builder<Actor, Integer, IndexFunctionTransformer> inputTransformers = ImmutableTable.builder(),
					outputTransformers = ImmutableTable.builder();
			for (Actor a : Iterables.filter(actors, WorkerActor.class)) {
				for (int j = 0; j < a.inputs().size(); ++j) {
//					String name = String.format("Core%dWorker%dInput%dIndexFxnTransformer", i, a.id(), j);
//					SwitchParameter<IndexFunctionTransformer> param = config.getParameter(name, SwitchParameter.class, IndexFunctionTransformer.class);
					inputTransformers.put(a, j, ift);
				}
				for (int j = 0; j < a.outputs().size(); ++j) {
//					String name = String.format("Core%dWorker%dOutput%dIndexFxnTransformer", i, a.id(), j);
//					SwitchParameter<IndexFunctionTransformer> param = config.getParameter(name, SwitchParameter.class, IndexFunctionTransformer.class);
					outputTransformers.put(a, j, ift);
				}
			}

			ImmutableMap.Builder<ActorGroup, Integer> unrollFactors = ImmutableMap.builder();
			for (ActorGroup g : groups) {
				if (g.isTokenGroup()) continue;
				IntParameter param = config.getParameter(String.format("UnrollCore%dGroup%d", i, g.id()), IntParameter.class);
				unrollFactors.put(g, param.getValue());
			}

			ssCores.add(new Core(CollectionUtils.union(steadyStateStorage, internalStorage), (table, wa) -> SWITCHING_STRATEGY.createSwitch(table, wa, config), unrollFactors.build(), inputTransformers.build(), outputTransformers.build()));
		}

		int throughputPerSteadyState = 0;
		for (ActorGroup g : groups)
			if (!g.isTokenGroup())
				ALLOCATION_STRATEGY.allocateGroup(g, Range.closedOpen(0, externalSchedule.get(g)), ssCores, config);
			else {
				assert g.actors().size() == 1;
				TokenActor ta = (TokenActor)g.actors().iterator().next();
				assert g.schedule().get(ta) == 1;
				ConcreteStorage storage = steadyStateStorage.get(Iterables.getOnlyElement(ta.isInput() ? g.outputs() : g.inputs()));
				int executions = externalSchedule.get(g);
				if (ta.isInput())
					readInstructions.add(makeReadInstruction(ta, storage, executions));
				else {
					writeInstructions.add(makeWriteInstruction(ta, storage, executions));
					throughputPerSteadyState += executions;
				}
			}
		ImmutableList.Builder<MethodHandle> steadyStateCodeBuilder = ImmutableList.builder();
		for (Core c : ssCores)
			if (!c.isEmpty())
				steadyStateCodeBuilder.add(c.code());
		//Provide at least one core of code, even if it doesn't do anything; the
		//blob host will still copy inputs to outputs.
		this.steadyStateCode = steadyStateCodeBuilder.build();
		if (steadyStateCode.isEmpty())
			this.steadyStateCode = ImmutableList.of(Combinators.nop());

		createMigrationInstructions();
		createDrainInstructions();

		Boolean reportThroughput = (Boolean)config.getExtraData("reportThroughput");
		if (reportThroughput != null && reportThroughput) {
			ReportThroughputInstruction rti = new ReportThroughputInstruction(throughputPerSteadyState);
			readInstructions.add(rti);
			writeInstructions.add(rti);
		}
	}

	private ReadInstruction makeReadInstruction(TokenActor a, ConcreteStorage cs, int count) {
		assert a.isInput();
		Storage s = Iterables.getOnlyElement(a.outputs());
		IndexFunction idxFxn = Iterables.getOnlyElement(a.outputIndexFunctions());
		ReadInstruction retval;
		if (count == 0)
			retval = new NopReadInstruction(a.token());
		else if (cs instanceof PeekableBufferConcreteStorage)
			retval = new PeekReadInstruction(a, count);
		else if (!s.type().isPrimitive() &&
				cs instanceof BulkWritableConcreteStorage &&
				contiguouslyIncreasing(idxFxn, 0, count)) {
			retval = new BulkReadInstruction(a, (BulkWritableConcreteStorage)cs, count);
		} else
			retval = new TokenReadInstruction(a, cs, count);
//		System.out.println("Made a "+retval+" for "+a.token());
		retval.init(precreatedBuffers);
		return retval;
	}

	private WriteInstruction makeWriteInstruction(TokenActor a, ConcreteStorage cs, int count) {
		assert a.isOutput();
		Storage s = Iterables.getOnlyElement(a.inputs());
		IndexFunction idxFxn = Iterables.getOnlyElement(a.inputIndexFunctions());
		WriteInstruction retval;
		if (count == 0)
			retval = new NopWriteInstruction(a.token());
		else if (!s.type().isPrimitive() &&
				cs instanceof BulkReadableConcreteStorage &&
				contiguouslyIncreasing(idxFxn, 0, count)) {
			retval = new BulkWriteInstruction(a, (BulkReadableConcreteStorage)cs, count);
		} else
			retval = new TokenWriteInstruction(a, cs, count);
//		System.out.println("Made a "+retval+" for "+a.token());
		retval.init(precreatedBuffers);
		return retval;
	}

	private boolean contiguouslyIncreasing(IndexFunction idxFxn, int start, int count) {
		try {
			int prev = idxFxn.applyAsInt(start);
			for (int i = start+1; i < count; ++i) {
				int next = idxFxn.applyAsInt(i);
				if (next != (prev + 1))
					return false;
				prev = next;
			}
			return true;
		} catch (Throwable ex) {
			throw new AssertionError("index functions should not throw", ex);
		}
	}

	/**
	 * Create migration instructions: Runnables that move live items from
	 * initialization to steady-state storage.
	 */
	private void createMigrationInstructions() {
		for (Storage s : initStorage.keySet()) {
			ConcreteStorage init = initStorage.get(s), steady = steadyStateStorage.get(s);
			if (steady instanceof PeekableBufferConcreteStorage)
				migrationInstructions.add(new PeekMigrationInstruction(
						s, (PeekableBufferConcreteStorage)steady));
			else
				migrationInstructions.add(new MigrationInstruction(s, init, steady, this));
		}
	}

	/**
	 * Create drain instructions, which collect live items from steady-state
	 * storage when draining.
	 */
	private void createDrainInstructions() {
		Map<Token, Pair<List<ConcreteStorage>, List<Integer>>> drainReads = new HashMap<>();
		for (Map.Entry<Token, Integer> e : postInitLiveness.entrySet())
			drainReads.put(e.getKey(), Pair.make(
					new ArrayList<>(Collections.nCopies(e.getValue(), null)),
					Ints.asList(new int[e.getValue()])));

		for (Actor a : actors) {
			for (int input = 0; input < a.inputs().size(); ++input) {
				ConcreteStorage storage = steadyStateStorage.get(a.inputs().get(input));
				StorageSlotList inputSlots = a.inputSlots(input);
				int[] bulk = getBulk(inputSlots.size());
				a.inputIndexFunctions().get(input).applyBulk(bulk);
				for (int index = 0; index < inputSlots.size(); ++index) {
					if (inputSlots.isDrainable(index)) {
						Pair<List<ConcreteStorage>, List<Integer>> dr = drainReads.get(inputSlots.getToken(index));
						int slotIndex = inputSlots.getIndex(index);
						ConcreteStorage old = dr.first.set(slotIndex, storage);
						assert old == null : "overwriting "+inputSlots.get(index);
						dr.second.set(slotIndex, bulk[index]);
					}
				}
			}
		}

		for (Map.Entry<Token, Pair<List<ConcreteStorage>, List<Integer>>> e : drainReads.entrySet()) {
			List<ConcreteStorage> storages = e.getValue().first;
			List<Integer> indices = e.getValue().second;
			assert !storages.contains(null) : "lost an element from "+e.getKey()+": "+e.getValue();
			if (!storages.isEmpty() && storages.get(0) instanceof PeekableBufferConcreteStorage) {
				assert storages.stream().allMatch(s -> s instanceof PeekableBufferConcreteStorage)
						: "mixed peeking and not? "+e.getKey()+": "+e.getValue();
				//we still create a drain instruction, but it does nothing
				storages.clear();
				indices = Ints.asList();
			} else
				assert storages.stream().noneMatch(s -> s instanceof PeekableBufferConcreteStorage)
						: "mixed peeking and not? "+e.getKey()+": "+e.getValue();
			drainInstructions.add(new XDrainInstruction(e.getKey(), storages, indices));
		}

		for (WorkerActor wa : Iterables.filter(actors, WorkerActor.class))
			drainInstructions.add(wa.stateHolder());
	}

	//<editor-fold defaultstate="collapsed" desc="Output index function adjust/restore">
	/**
	 * Adjust output index functions to avoid overwriting items in external
	 * storage.  For any actor writing to external storage, we find the
	 * first item that doesn't hit the live index set and add that many
	 * (making that logical item 0 for writers).
	 * @param liveIndexExtractor a function that computes the relevant live
	 * index set for the given external storage
	 * @return the old output index functions, to be restored later
	 */
	private ImmutableMap<Actor, ImmutableList<IndexFunction>> adjustOutputIndexFunctions(Function<Storage, Set<Integer>> liveIndexExtractor) {
		ImmutableMap.Builder<Actor, ImmutableList<IndexFunction>> backup = ImmutableMap.builder();
		for (Actor a : actors) {
			backup.put(a, ImmutableList.copyOf(a.outputIndexFunctions()));
			for (int i = 0; i < a.outputs().size(); ++i) {
				if (a.push(i) == 0) continue; //No writes -- nothing to adjust.
				Storage s = a.outputs().get(i);
				if (s.isInternal())
					continue;
				Set<Integer> liveIndices = liveIndexExtractor.apply(s);
				assert liveIndices != null : s +" "+liveIndexExtractor;
				IndexFunction idxFxn = a.outputIndexFunctions().get(i);
				int offset = GeneralBinarySearch.binarySearch(o -> liveIndices.contains(idxFxn.applyAsInt(o)), 0);
				//Check future indices are also open (e.g., that we aren't
				//alternating hole/not-hole).
				for (int check = 0; check < 100; ++check)
					assert !liveIndices.contains(a.translateOutputIndex(i, offset + check)) : check;
				final int finalOffset = offset;
				a.outputIndexFunctions().set(i, a.outputIndexFunctions().get(i).compose(new AdditionIndexFunction(finalOffset)));
			}
		}
		return backup.build();
	}

	/**
	 * Restores output index functions from a backup returned from
	 * {@link #adjustOutputIndexFunctions(com.google.common.base.Function)}.
	 * @param backup the backup to restore
	 */
	private void restoreOutputIndexFunctions(ImmutableMap<Actor, ImmutableList<IndexFunction>> backup) {
		for (Actor a : actors) {
			ImmutableList<IndexFunction> oldFxns = backup.get(a);
			assert oldFxns != null : "no backup for "+a;
			assert oldFxns.size() == a.outputIndexFunctions().size() : "backup for "+a+" is wrong size";
			Collections.copy(a.outputIndexFunctions(), oldFxns);
		}
	}
	//</editor-fold>

	private ImmutableMap<Storage, ConcreteStorage> createStorage(boolean internal, StorageFactory factory) {
		ImmutableMap.Builder<Storage, ConcreteStorage> builder = ImmutableMap.builder();
		for (Storage s : storage)
			if (s.isInternal() == internal)
				builder.put(s, factory.make(s));
		return builder.build();
	}

	/**
	 * Creates special ConcreteStorage implementations for PeekableBuffer and
	 * PokeableBuffer, or falls back to the given factory.
	 */
	private final class PeekPokeStorageFactory implements StorageFactory {
		private final StorageFactory fallback;
		private final boolean usePeekableBuffer;
		private PeekPokeStorageFactory(StorageFactory fallback) {
			this.fallback = fallback;
			SwitchParameter<Boolean> usePeekableBufferParam = config.getParameter("UsePeekableBuffer", SwitchParameter.class, Boolean.class);
			this.usePeekableBuffer = usePeekableBufferParam.getValue();
		}
		@Override
		public ConcreteStorage make(Storage storage) {
			if (storage.id().isOverallInput() && overallInputBuffer instanceof PeekableBuffer && usePeekableBuffer)
				return PeekableBufferConcreteStorage.factory(ImmutableMap.of(storage.id(), (PeekableBuffer)overallInputBuffer)).make(storage);
			//TODO: PokeableBuffer
			return fallback.make(storage);
		}
	}

	private static final class MigrationInstruction implements Runnable {
		private final ConcreteStorage init, steady;
		private final int[] indicesToMigrate;
		private MigrationInstruction(Storage storage, ConcreteStorage init, ConcreteStorage steady, Compiler2 compiler) {
			this.init = init;
			this.steady = steady;
			Set<Integer> builder = new HashSet<>();
			for (Actor a : storage.downstream())
				for (int i = 0; i < a.inputs().size(); ++i)
					if (a.inputs().get(i).equals(storage)) {
						StorageSlotList inputSlots = a.inputSlots(i);
						int[] bulk = compiler.getBulk(inputSlots.size());
						a.inputIndexFunctions().get(i).applyBulk(bulk);
						for (int idx = 0; idx < inputSlots.size(); ++idx)
							if (inputSlots.isLive(idx))
								builder.add(bulk[idx]);
					}
			this.indicesToMigrate = Ints.toArray(builder);
			//TODO: we used to sort here (using ImmutableSortedSet).  Does it matter?
		}
		@Override
		public void run() {
			init.sync();
			for (int i : indicesToMigrate)
				steady.write(i, init.read(i));
			steady.sync();
		}
	}

	private static final class PeekMigrationInstruction implements Runnable {
		private final PeekableBuffer buffer;
		private final int itemsConsumedDuringInit;
		private PeekMigrationInstruction(Storage storage, PeekableBufferConcreteStorage steady) {
			this.buffer = steady.buffer();
			this.itemsConsumedDuringInit = steady.minReadIndex();
		}
		@Override
		public void run() {
			buffer.consume(itemsConsumedDuringInit);
		}
	}

	/**
	 * The X doesn't stand for anything.  I just needed a different name.
	 */
	private static final class XDrainInstruction implements DrainInstruction {
		private final Token token;
		private final ConcreteStorage[] storage;
		private final int[] storageSelector, index;
		private XDrainInstruction(Token token, List<ConcreteStorage> storages, List<Integer> indices) {
			assert storages.size() == indices.size() : String.format("%s %s %s", token, storages, indices);
			this.token = token;
			Set<ConcreteStorage> set = new HashSet<>(storages);
			this.storage = set.toArray(new ConcreteStorage[set.size()]);
			this.storageSelector = new int[indices.size()];
			this.index = Ints.toArray(indices);
			for (int i = 0; i < indices.size(); ++i)
				storageSelector[i] = Arrays.asList(storage).indexOf(storages.get(i));
		}
		@Override
		public Map<Token, Object[]> call() {
			Object[] data = new Object[index.length];
			int idx = 0;
			for (int i = 0; i < index.length; ++i)
				data[idx++] = storage[storageSelector[i]].read(index[i]);
			return ImmutableMap.of(token, data);
		}
	}

	/**
	 * PeekReadInstruction implements special handling for PeekableBuffer.
	 */
	private static final class PeekReadInstruction implements ReadInstruction {
		private final Token token;
		private final int count;
		private PeekableBuffer buffer;
		private PeekReadInstruction(TokenActor a, int count) {
			assert a.isInput() : a;
			this.token = a.token();
			//If we "read" count new items, the maximum available index is given
			//by the output index function.
			this.count = a.translateOutputIndex(0, count);
		}
		@Override
		public void init(Map<Token, Buffer> buffers) {
			if (!buffers.containsKey(token)) return;
			if (buffer != null)
				checkState(buffers.get(token) == buffer, "reassigning %s from %s to %s", token, buffer, buffers.get(token));
			this.buffer = (PeekableBuffer)buffers.get(token);
		}
		@Override
		public Map<Token, Integer> getMinimumBufferCapacity() {
			//This shouldn't matter because we've already created the buffers.
			return ImmutableMap.of(token, count);
		}
		@Override
		public boolean load() {
			//Ensure data is present for reading.
			return buffer.size() >= count;
		}
		@Override
		public Map<Token, Object[]> unload() {
			//Data is not consumed from the underlying buffer until it's
			//adjusted, so no work is necessary here.
			return ImmutableMap.of();
		}
	}

	private static final class BulkReadInstruction implements ReadInstruction {
		private final Token token;
		private final BulkWritableConcreteStorage storage;
		private final int index, count;
		private Buffer buffer;
		private BulkReadInstruction(TokenActor a, BulkWritableConcreteStorage storage, int count) {
			assert a.isInput() : a;
			this.token = a.token();
			this.storage = storage;
			this.index = a.translateOutputIndex(0, 0);
			this.count = count;
		}
		@Override
		public void init(Map<Token, Buffer> buffers) {
			if (!buffers.containsKey(token)) return;
			if (buffer != null)
				checkState(buffers.get(token) == buffer, "reassigning %s from %s to %s", token, buffer, buffers.get(token));
			this.buffer = buffers.get(token);
		}
		@Override
		public Map<Token, Integer> getMinimumBufferCapacity() {
			return ImmutableMap.of(token, count);
		}
		@Override
		public boolean load() {
			if (buffer.size() < count)
				return false;
			storage.bulkWrite(buffer, index, count);
			storage.sync();
			return true;
		}
		@Override
		public Map<Token, Object[]> unload() {
			Object[] data = new Object[count];
			for (int i = index; i < count; ++i) {
				data[i - index] = storage.read(i);
			}
			return ImmutableMap.of(token, data);
		}
	}

	/**
	 * TODO: consider using read/write handles instead of read(), write()?
	 */
	private static final class TokenReadInstruction implements ReadInstruction {
		private final Token token;
		private final IndexFunction idxFxn;
		private final ConcreteStorage storage;
		private final int count;
		private Buffer buffer;
		private TokenReadInstruction(TokenActor a, ConcreteStorage storage, int count) {
			assert a.isInput() : a;
			this.token = a.token();
			this.storage = storage;
			this.idxFxn = Iterables.getOnlyElement(a.outputIndexFunctions());
			this.count = count;
		}
		@Override
		public void init(Map<Token, Buffer> buffers) {
			if (!buffers.containsKey(token)) return;
			if (buffer != null)
				checkState(buffers.get(token) == buffer, "reassigning %s from %s to %s", token, buffer, buffers.get(token));
			this.buffer = buffers.get(token);
		}
		@Override
		public Map<Token, Integer> getMinimumBufferCapacity() {
			return ImmutableMap.of(token, count);
		}
		@Override
		public boolean load() {
			Object[] data = new Object[count];
			if (!buffer.readAll(data))
				return false;
			for (int i = 0; i < data.length; ++i) {
				int idx;
				try {
					idx = idxFxn.applyAsInt(i);
				} catch (Throwable ex) {
					throw new AssertionError("Can't happen! Index functions should not throw", ex);
				}
				storage.write(idx, data[i]);
			}
			storage.sync();
			return true;
		}
		@Override
		public Map<Token, Object[]> unload() {
			Object[] data = new Object[count];
			for (int i = 0; i < data.length; ++i) {
				int idx;
				try {
					idx = idxFxn.applyAsInt(i);
				} catch (Throwable ex) {
					throw new AssertionError("Can't happen! Index functions should not throw", ex);
				}
				data[i] = storage.read(idx);
			}
			return ImmutableMap.of(token, data);
		}
	}

	/**
	 * Doesn't read anything, but does respond to getMinimumBufferCapacity().
	 */
	private static final class NopReadInstruction implements ReadInstruction {
		private final Token token;
		private NopReadInstruction(Token token) {
			this.token = token;
		}
		@Override
		public void init(Map<Token, Buffer> buffers) {
		}
		@Override
		public Map<Token, Integer> getMinimumBufferCapacity() {
			return ImmutableMap.of(token, 0);
		}
		@Override
		public boolean load() {
			return true;
		}
		@Override
		public Map<Token, Object[]> unload() {
			return ImmutableMap.of();
		}
	}

	private static final class BulkWriteInstruction implements WriteInstruction {
		private final Token token;
		private final BulkReadableConcreteStorage storage;
		private final int index, count;
		private Buffer buffer;
		private int written;
		private BulkWriteInstruction(TokenActor a, BulkReadableConcreteStorage storage, int count) {
			assert a.isOutput(): a;
			this.token = a.token();
			this.storage = storage;
			this.index = a.translateInputIndex(0, 0);
			this.count = count;
		}
		@Override
		public void init(Map<Token, Buffer> buffers) {
			if (!buffers.containsKey(token)) return;
			if (buffer != null)
				checkState(buffers.get(token) == buffer, "reassigning %s from %s to %s", token, buffer, buffers.get(token));
			this.buffer = buffers.get(token);
		}
		@Override
		public Map<Token, Integer> getMinimumBufferCapacity() {
			return ImmutableMap.of(token, count);
		}
		@Override
		public Boolean call() {
			written += storage.bulkRead(buffer, index + written, count);
			if (written < count)
				return false;
			written = 0;
			return true;
		}
	}

	/**
	 * TODO: consider using read handles instead of read()?
	 */
	private static final class TokenWriteInstruction implements WriteInstruction {
		private final Token token;
		private final IndexFunction idxFxn;
		private final ConcreteStorage storage;
		private final int count;
		private Buffer buffer;
		private int written;
		private TokenWriteInstruction(TokenActor a, ConcreteStorage storage, int count) {
			assert a.isOutput() : a;
			this.token = a.token();
			this.storage = storage;
			this.idxFxn = Iterables.getOnlyElement(a.inputIndexFunctions());
			this.count = count;
		}
		@Override
		public void init(Map<Token, Buffer> buffers) {
			if (!buffers.containsKey(token)) return;
			if (buffer != null)
				checkState(buffers.get(token) == buffer, "reassigning %s from %s to %s", token, buffer, buffers.get(token));
			this.buffer = buffers.get(token);
		}
		@Override
		public Map<Token, Integer> getMinimumBufferCapacity() {
			return ImmutableMap.of(token, count);
		}
		@Override
		public Boolean call() {
			Object[] data = new Object[count];
			for (int i = 0; i < count; ++i) {
				int idx;
				try {
					idx = idxFxn.applyAsInt(i);
				} catch (Throwable ex) {
					throw new AssertionError("Can't happen! Index functions should not throw", ex);
				}
				data[i] = storage.read(idx);
			}
			written += buffer.write(data, written, data.length-written);
			if (written < count)
				return false;
			written = 0;
			return true;
		}
	}

	/**
	 * Doesn't write anything, but does respond to getMinimumBufferCapacity().
	 */
	private static final class NopWriteInstruction implements WriteInstruction {
		private final Token token;
		private NopWriteInstruction(Token token) {
			this.token = token;
		}
		@Override
		public void init(Map<Token, Buffer> buffers) {
		}
		@Override
		public Map<Token, Integer> getMinimumBufferCapacity() {
			return ImmutableMap.of(token, 0);
		}
		@Override
		public Boolean call() {
			return true;
		}
	}

	/**
	 * Writes initial data into init storage, or "unloads" it (just returning it
	 * as it was in the DrainData, not actually reading the storage) if we drain
	 * during init.  (Any remaining data after init will be migrated as normal.)
	 * There's only one of these per blob because it returns all the data, and
	 * it should be the first initReadInstruction.
	 */
	private static final class InitDataReadInstruction implements ReadInstruction {
		private final ImmutableMap<ConcreteStorage, ImmutableList<Pair<ImmutableList<Object>, IndexFunction>>> toWrite;
		private final ImmutableMap<Token, ImmutableList<Object>> initialStateDataMap;
		private InitDataReadInstruction(Map<Storage, ConcreteStorage> initStorage, ImmutableMap<Token, ImmutableList<Object>> initialStateDataMap) {
			ImmutableMap.Builder<ConcreteStorage, ImmutableList<Pair<ImmutableList<Object>, IndexFunction>>> toWriteBuilder = ImmutableMap.builder();
			for (Map.Entry<Storage, ConcreteStorage> e : initStorage.entrySet()) {
				Storage s = e.getKey();
				if (s.isInternal()) continue;
				if (s.initialData().isEmpty()) continue;
				toWriteBuilder.put(e.getValue(), ImmutableList.copyOf(s.initialData()));
			}
			this.toWrite = toWriteBuilder.build();
			this.initialStateDataMap = initialStateDataMap;
		}
		@Override
		public void init(Map<Token, Buffer> buffers) {
		}
		@Override
		public Map<Token, Integer> getMinimumBufferCapacity() {
			return ImmutableMap.of();
		}
		@Override
		public boolean load() {
			for (Map.Entry<ConcreteStorage, ImmutableList<Pair<ImmutableList<Object>, IndexFunction>>> e : toWrite.entrySet())
				for (Pair<ImmutableList<Object>, IndexFunction> p : e.getValue())
					for (int i = 0; i < p.first.size(); ++i) {
						int idx;
						try {
							idx = p.second.applyAsInt(i);
						} catch (Throwable ex) {
							throw new AssertionError("Can't happen! Index functions should not throw", ex);
						}
						e.getKey().write(idx, p.first.get(i));
					}
			return true;
		}
		@Override
		public Map<Token, Object[]> unload() {
			Map<Token, Object[]> r = new HashMap<>();
			for (Map.Entry<Token, ImmutableList<Object>> e : initialStateDataMap.entrySet())
				r.put(e.getKey(), e.getValue().toArray());
			return r;
		}
	}

	private static final class ReportThroughputInstruction implements ReadInstruction, WriteInstruction {
		private static final long WARMUP_NANOS = TimeUnit.NANOSECONDS.convert(10, TimeUnit.SECONDS);
		private static final long TIMING_NANOS = TimeUnit.NANOSECONDS.convert(5, TimeUnit.SECONDS);
		private final long throughputPerSteadyState;
		private int steadyStates = 0;
		private long firstNanoTime = Long.MIN_VALUE, afterWarmupNanoTime = Long.MIN_VALUE;
		private ReportThroughputInstruction(long throughputPerSteadyState) {
			this.throughputPerSteadyState = throughputPerSteadyState;
		}
		@Override
		public void init(Map<Token, Buffer> buffers) {}
		@Override
		public Map<Token, Integer> getMinimumBufferCapacity() {
			return ImmutableMap.of();
		}
		@Override
		public Map<Token, Object[]> unload() {
			return ImmutableMap.of();
		}

		@Override
		public boolean load() {
			long currentTime = time();
			if (firstNanoTime == Long.MIN_VALUE)
				firstNanoTime = currentTime;
			else if (afterWarmupNanoTime == Long.MIN_VALUE && currentTime - firstNanoTime > WARMUP_NANOS)
				afterWarmupNanoTime = currentTime;
			return true;
		}
		@Override
		public Boolean call() {
			if (afterWarmupNanoTime != Long.MIN_VALUE) {
				++steadyStates;
				long currentTime = time();
				long elapsed = currentTime - afterWarmupNanoTime;
				if (elapsed > TIMING_NANOS) {
					long itemsOutput = steadyStates * throughputPerSteadyState;
					System.out.format("%d/%d/%d/%d#%n", steadyStates, itemsOutput, elapsed, elapsed/itemsOutput);
					System.out.flush();
					System.exit(0);
				}
			}
			return true;
		}
		private static long time() {
//			return System.currentTimeMillis()*1000000;
			return System.nanoTime();
		}
	}

	/**
	 * Creates the blob host.  This mostly involves shuffling our state into the
	 * form the blob host wants.
	 * @return the blob
	 */
	public Blob instantiateBlob() {
		ImmutableSortedSet.Builder<Token> inputTokens = ImmutableSortedSet.naturalOrder(),
				outputTokens = ImmutableSortedSet.naturalOrder();
		for (TokenActor ta : Iterables.filter(actors, TokenActor.class))
			(ta.isInput() ? inputTokens : outputTokens).add(ta.token());
		ImmutableList.Builder<MethodHandle> storageAdjusts = ImmutableList.builder();
		for (ConcreteStorage s : steadyStateStorage.values())
			storageAdjusts.add(s.adjustHandle());
		return new Compiler2BlobHost(workers, config,
				inputTokens.build(), outputTokens.build(),
				initCode, steadyStateCode,
				storageAdjusts.build(),
				initReadInstructions, initWriteInstructions, migrationInstructions,
				readInstructions, writeInstructions, drainInstructions,
				precreatedBuffers);
	}

	private final Map<Integer, int[]> bulkCache = new HashMap<>();
	private int[] getBulk(int size) {
		int[] bulk = bulkCache.computeIfAbsent(size, int[]::new);
		for (int i = 0; i < bulk.length; ++i)
			bulk[i] = i;
		return bulk;
	}

	public static void main(String[] args) {
		StreamCompiler sc;
		Benchmark bm;
		if (args.length == 3) {
			String benchmarkName = args[0];
			int cores = Integer.parseInt(args[1]);
			int multiplier = Integer.parseInt(args[2]);
			sc = new Compiler2StreamCompiler().maxNumCores(cores).multiplier(multiplier);
			bm = Benchmarker.getBenchmarkByName(benchmarkName);
		} else {
			sc = new Compiler2StreamCompiler().multiplier(384).maxNumCores(4);
			bm = new FMRadio.FMRadioBenchmarkProvider().iterator().next();
		}
		Benchmarker.runBenchmark(bm, sc).get(0).print(System.out);
	}

	private void printDot(String stage) {
		try (FileWriter fw = new FileWriter(stage+".dot");
				BufferedWriter bw = new BufferedWriter(fw)) {
			bw.write("digraph {\n");
			for (ActorGroup g : groups) {
				bw.write("subgraph cluster_"+Integer.toString(g.id()).replace('-', '_')+" {\n");
				for (Actor a : g.actors())
					bw.write(String.format("\"%s\";\n", nodeName(a)));
				bw.write("label = \"x"+externalSchedule.get(g)+"\";\n");
				bw.write("}\n");
			}
			for (ActorGroup g : groups) {
				for (Actor a : g.actors()) {
					for (Storage s : a.outputs())
						for (Actor b : s.downstream())
							bw.write(String.format("\"%s\" -> \"%s\";\n", nodeName(a), nodeName(b)));
				}
			}
			bw.write("}\n");
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}

	private String nodeName(Actor a) {
		if (a instanceof TokenActor)
			return (((TokenActor)a).isInput()) ? "input" : "output";
		WorkerActor wa = (WorkerActor)a;
		String workerClassName = wa.worker().getClass().getSimpleName();
		return workerClassName.replaceAll("[a-z]", "") + "@" + Integer.toString(wa.id()).replace('-', '_');
	}
}


text:
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package edu.wpi.first.wpilibj.paradigm;

import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.Talon;
import edu.wpi.first.wpilibj.Solenoid;
import edu.wpi.first.wpilibj.Encoder;
import edu.wpi.first.wpilibj.Timer;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;

/**
 *
 * @author Programming
 */
public class DriveTrain {

    //static variables can benefit compile size
    final int LEFT_PORT = 1;
    final int RIGHT_PORT = 2;
    final int SHIFT_PORT_LOW = 1;
    final int SHIFT_PORT_HIGH = 2;
    final int SHIFT_MODULE = 1;
    boolean needsShoot = false;

    double joyStickX;
    double joyStickY;

    double leftPow;
    double rightPow;
    double totalSpeed;
    long sleeptime = 1000;

    double speedMult = 1;
    double fixNum;
    double maxLeftEncoderRate;
    double maxRightEncoderRate;
    double ratio = 1;
    double rightEncoderFix;
    double leftEncoderFix;
    long sleepTime = 0100;
    boolean isHighGear = false; //Robot starts in low gear
    boolean nemo = false;
    boolean isLeftHigher = true;
    double leftSpeed = 0;
    double rightSpeed = 0;
    final double encoderDeadzone = 1000;
    final double encoderWaitTime = 168;
    double leftChildProofSetter;
    double rightChildProofSetter;
    boolean childProofConfirmed = false;
    private static final double DISTANCE_PER_PULSE = 0.0006708;
    boolean previousTriggerPressed = false; //what the trigger value was before the current press, allows for trigger to stay pressed w/o flipping
    double previousLeftPow = 0;
    double previousRightPow = 0;
    double coasting = 1;
    private static final double teleopRamp = 0.08;

    public Talon leftTalons;
    public Talon rightTalons;
    private Solenoid gearShiftLow;
    private Solenoid gearShiftHigh;
    Encoder leftEncoder;
    Encoder rightEncoder;
    Timer timer;
    OperatorInputs operatorInputs;

    public DriveTrain(OperatorInputs _operatorInputs) {
        this.operatorInputs = _operatorInputs;
        this.leftTalons = new Talon(LEFT_PORT);
        this.rightTalons = new Talon(RIGHT_PORT);
        this.gearShiftLow = new Solenoid(SHIFT_MODULE, SHIFT_PORT_LOW);
        this.gearShiftHigh = new Solenoid(SHIFT_MODULE, SHIFT_PORT_HIGH);
        this.leftEncoder = new Encoder(3, 4);
        this.rightEncoder = new Encoder(5, 6);
        this.timer = new Timer();
        //Start all wheels off
        leftTalons.set(0);
        rightTalons.set(0);
        //Starts in low gear
        gearShiftLow.set(isHighGear);
        gearShiftHigh.set(!isHighGear);
        leftEncoder.setDistancePerPulse(-DISTANCE_PER_PULSE);
        rightEncoder.setDistancePerPulse(DISTANCE_PER_PULSE);

    }

    public void rampLeftPower(double desiredPow, double rampSpeed) { //Makes it so that robot can't go stop to full
        if (Math.abs(desiredPow - previousLeftPow) < rampSpeed) {
            previousLeftPow = desiredPow;
        } else if (previousLeftPow < desiredPow) {
            previousLeftPow += rampSpeed;
        } else if (previousLeftPow > desiredPow) {
            previousLeftPow -= rampSpeed;
        }
        leftTalons.set(-previousLeftPow);

    }

    public void rampRightPower(double desiredPow, double rampSpeed) { //Makes it so that robot can't go stop to full
        if (Math.abs(desiredPow - previousRightPow) < rampSpeed) {
            previousRightPow = desiredPow;
        } else if (previousRightPow < desiredPow) {
            previousRightPow += rampSpeed;
        } else if (previousRightPow > desiredPow) {
            previousRightPow -= rampSpeed;
        }
        rightTalons.set(previousRightPow);

    }

    public void resetEncoders() { //Resets current raw encoder value to 0
        leftEncoder.reset();
        rightEncoder.reset();
        gearShiftHigh.set(isHighGear);
        gearShiftLow.set(!isHighGear);
    }

    public boolean driveStraight(double distance, double firingDistance, double speed/*, Shooter shoot*/) { //Controls robot during autonomous

        double batteryVoltage = DriverStation.getInstance().getBatteryVoltage();
        if (rightEncoder.getDistance() < distance) {
            rampLeftPower(speed, 0.5 / DriverStation.getInstance().getBatteryVoltage());
            rampRightPower(speed, 0.5 / DriverStation.getInstance().getBatteryVoltage());
            needsShoot = true;
            timer.start();
            return true;
        } else if (previousRightPow != 0 || previousLeftPow != 0) {
            rampLeftPower(0, 0.5 / DriverStation.getInstance().getBatteryVoltage());
            rampRightPower(0, 0.5 / DriverStation.getInstance().getBatteryVoltage());
            timer.reset();
            return true;
        } else {
            rampLeftPower(0, 0.5 / DriverStation.getInstance().getBatteryVoltage());
            rampRightPower(0, 0.5 / DriverStation.getInstance().getBatteryVoltage());
            return false;
            /*
            if (timer.get() > 1) {
                shoot.quickShoot(1.0, (11.0 / batteryVoltage) > 0.95 ? 0.95 : (11.0 / batteryVoltage), 0.01, needsShoot);
                needsShoot = false;
            }
                    */
        }
    }

    public double getRightPulses() { //Returns raw value of right talon encoder
        return rightEncoder.getRaw();
    }

    public double getLeftPulses() { //Returns raw value of left talon encoder
        return leftEncoder.getRaw();
    }

    public double getRightEncoderDistance() { //Returns the distance per pulse of the right encoder
        return rightEncoder.getDistance();
    }

    public double getLeftEncoderDistance() { //Returns the distance per pulse of the left encoder
        return leftEncoder.getDistance();
    }

    public double fix(double v) {
        return v / fixNum;
    }

    public double LeftMotor() {
        //moved leftSpeed to class scope, it is being set in setPower()
        double fixLeftPow = fix(leftPow);
        //moved rightSpeed to class scope, it is being set in setPower()
        double fixRightPow = fix(rightPow);

        if (leftPow != 0 && rightPow != 0) {
            maxLeftEncoderRate = Math.abs(leftSpeed / leftPow);
            if (Math.min(Math.abs(leftSpeed), Math.abs(rightSpeed)) > encoderDeadzone) {
                breakTime();
            }
            if (isLeftHigher) {
                fixLeftPow = ratio * fixLeftPow;
            }
            leftChildProofSetter = Math.abs(fixLeftPow);
        }

        return (fixLeftPow);
    }

    public double RightMotor() {
        //moved leftSpeed to class scope, it is being set in setPower()
        double fixLeftPow = fix(leftPow);
        //moved rightSpeed to class scope, it is being set in setPower()
        double fixRightPow = fix(rightPow);

        if (leftPow != 0 && rightPow != 0) {
            maxRightEncoderRate = Math.abs(rightSpeed / rightPow);
            if (Math.min(Math.abs(leftSpeed), Math.abs(rightSpeed)) > encoderDeadzone) {
                breakTime();
            }
            if (!isLeftHigher) {
                fixRightPow = ratio * fixRightPow;
            }
            rightChildProofSetter = Math.abs(fixRightPow);
        }

        return (fixRightPow);

    }

    public void compareEncoders() { //If left motor speed is bigger than the right motor speed return true, else false
        if (maxRightEncoderRate > maxLeftEncoderRate) {
            ratio = maxLeftEncoderRate / maxRightEncoderRate;
            leftEncoderFix = maxRightEncoderRate * ratio;
            isLeftHigher = false;

        } else if (maxLeftEncoderRate > maxRightEncoderRate) {
            ratio = maxRightEncoderRate / maxLeftEncoderRate;
            rightEncoderFix = maxLeftEncoderRate * ratio;
            isLeftHigher = true;
        } else {
            ratio = 1;

        }
    }

    public void breakTime() {
        SmartDashboard.putNumber("Ratio", ratio);
        SmartDashboard.putBoolean("Left > Right", isLeftHigher);
        SmartDashboard.putNumber("Timer time", timer.get());
        if (timer.get() > encoderWaitTime) {
            compareEncoders();
            timer.reset();
        }
    }

    public void setPower() {
        joyStickX = operatorInputs.joystickX();
        joyStickY = operatorInputs.joystickY();
        //set fixnum = the maxiumum value for this angle on the joystick
        if (joyStickX == 0 || joyStickY == 0) {
            fixNum = 1;
        } else {
            if (Math.abs(joyStickX) > Math.abs(joyStickY)) {
                double fixNumMult = 1 / Math.abs(joyStickX);
                fixNum = Math.abs(joyStickY) * fixNumMult + 1;
            } else {
                double fixNumMult = 1 / Math.abs(joyStickY);
                fixNum = Math.abs(joyStickX) * fixNumMult + 1;
            }
        }
        leftPow = -joyStickY + joyStickX;
        rightPow = -joyStickY - joyStickX;
        leftSpeed = leftEncoder.getRate();
        rightSpeed = rightEncoder.getRate();

        rampLeftPower(coasting * LeftMotor(), 0.5 / DriverStation.getInstance().getBatteryVoltage()); //Left Motors are forward=negative
        SmartDashboard.putNumber("JoystickX", joyStickX);
        SmartDashboard.putNumber("LeftTalons", -leftTalons.get()); //Left Motors are forward=negative
        SmartDashboard.putNumber("LeftSpeed", -leftSpeed); //Left Motors are forward=negative

        rampRightPower(coasting * RightMotor(), 0.5 / DriverStation.getInstance().getBatteryVoltage()); //Right Motors are forward=positive
        SmartDashboard.putNumber("JoystickY", joyStickY);
        SmartDashboard.putNumber("RightTalons", rightTalons.get()); //Right Motors are forward=positive
        SmartDashboard.putNumber("RightSpeed", rightSpeed); //Right Motors are forward=positive
        //System.out.println("High gear :" + isHighGear);
    }

    public void shift() {//current setting is start in high gear
        boolean triggerPressed = operatorInputs.joystickTriggerPressed();
        //System.out.println("Trigger Pressed :" + triggerPressed);
        if (triggerPressed && !previousTriggerPressed) {
            isHighGear = !isHighGear;
            //Shifts gear
            gearShiftHigh.set(isHighGear);
            gearShiftLow.set(!isHighGear);
        }

        previousTriggerPressed = triggerPressed;

    }

    public void setSpeedPositive() {
        totalSpeed = (leftPow + rightPow) / 2;
        if (isHighGear = true) {
            totalSpeed = (leftPow + rightPow);
        }
        if (totalSpeed < 0) {
            totalSpeed = -totalSpeed;
        }
    }

    public void childProofing() { //Low to high and speed, High to low when speed is under a certain value

        if (rightChildProofSetter < .75 && leftChildProofSetter < .75) {
            childProofConfirmed = true;
        } else {
            childProofConfirmed = false;
        }
    }

    public void setCoasting(double newCoasting) {
        coasting = newCoasting;
    }

}

redpajama_set_name: RedPajamaGithub


text:
(function() {
  'use strict';

  /**
   * Extend an Object with another Object's properties.
   *
   * The source objects are specified as additional arguments.
   *
   * @param dst Object the object to extend.
   *
   * @return Object the final object.
   */
  var _extend = function(dst) {
    var sources = Array.prototype.slice.call(arguments, 1);
    for (var i=0; i<sources.length; ++i) {
      var src = sources[i];
      for (var p in src) {
        if (src.hasOwnProperty(p)) dst[p] = src[p];
      }
    }
    return dst;
  };

  /**
   * Based on the algorithm at http://en.wikipedia.org/wiki/Levenshtein_distance.
   */
  var Levenshtein = {
    /**
     * Calculate levenshtein distance of the two strings.
     *
     * @param str1 String the first string.
     * @param str2 String the second string.
     * @return Integer the levenshtein distance (0 and above).
     */
    get: function(str1, str2) {
      // base cases
      if (str1 === str2) return 0;
      if (str1.length === 0) return str2.length;
      if (str2.length === 0) return str1.length;

      // two rows
      var prevRow  = new Array(str2.length + 1),
          curCol, nextCol, i, j, tmp;

      // initialise previous row
      for (i=0; i<prevRow.length; ++i) {
        prevRow[i] = i;
      }

      // calculate current row distance from previous row
      for (i=0; i<str1.length; ++i) {
        nextCol = i + 1;

        for (j=0; j<str2.length; ++j) {
          curCol = nextCol;

          // substution
          nextCol = prevRow[j] + ( (str1.charAt(i) === str2.charAt(j)) ? 0 : 1 );
          // insertion
          tmp = curCol + 1;
          if (nextCol > tmp) {
            nextCol = tmp;
          }
          // deletion
          tmp = prevRow[j + 1] + 1;
          if (nextCol > tmp) {
            nextCol = tmp;
          }

          // copy current col value into previous (in preparation for next iteration)
          prevRow[j] = curCol;
        }

        // copy last col value into previous (in preparation for next iteration)
        prevRow[j] = nextCol;
      }

      return nextCol;
    },

    /**
     * Asynchronously calculate levenshtein distance of the two strings.
     *
     * @param str1 String the first string.
     * @param str2 String the second string.
     * @param cb Function callback function with signature: function(Error err, int distance)
     * @param [options] Object additional options.
     * @param [options.progress] Function progress callback with signature: function(percentComplete)
     */
    getAsync: function(str1, str2, cb, options) {
      options = _extend({}, {
        progress: null
      }, options);

      // base cases
      if (str1 === str2) return cb(null, 0);
      if (str1.length === 0) return cb(null, str2.length);
      if (str2.length === 0) return cb(null, str1.length);

      // two rows
      var prevRow  = new Array(str2.length + 1),
          curCol, nextCol,
          i, j, tmp,
          startTime, currentTime;

      // initialise previous row
      for (i=0; i<prevRow.length; ++i) {
        prevRow[i] = i;
      }

      nextCol = 1;
      i = 0;
      j = -1;

      var __calculate = function() {
        // reset timer
        startTime = new Date().valueOf();
        currentTime = startTime;

        // keep going until one second has elapsed
        while (currentTime - startTime < 1000) {
          // reached end of current row?
          if (str2.length <= (++j)) {
            // copy current into previous (in preparation for next iteration)
            prevRow[j] = nextCol;

            // if already done all chars
            if (str1.length <= (++i)) {
              return cb(null, nextCol);
            }
            // else if we have more left to do
            else {
              nextCol = i + 1;
              j = 0;
            }
          }

          // calculation
          curCol = nextCol;

          // substution
          nextCol = prevRow[j] + ( (str1.charAt(i) === str2.charAt(j)) ? 0 : 1 );
          // insertion
          tmp = curCol + 1;
          if (nextCol > tmp) {
            nextCol = tmp;
          }
          // deletion
          tmp = prevRow[j + 1] + 1;
          if (nextCol > tmp) {
            nextCol = tmp;
          }

          // copy current into previous (in preparation for next iteration)
          prevRow[j] = curCol;

          // get current time
          currentTime = new Date().valueOf();
        }

        // send a progress update?
        if (null !== options.progress) {
          try {
            options.progress.call(null, (i * 100.0/ str1.length));
          } catch (err) {
            return cb('Progress callback: ' + err.toString());
          }
        }

        // next iteration
        setTimeout(__calculate(), 0);
      };

      __calculate();
    }

  };

  // angular
  if (typeof angular !== "undefined" && angular != null) {
    angular
      .module('ngFastLevenshtein', [])
      .factory('fastLevenshteinService', function () {
        return Levenshtein;
      });
  }
  // amd
  else if (typeof define !== "undefined" && define !== null && define.amd) {
    define(function() {
      return Levenshtein;
    });
  }
  // commonjs
  else if (typeof module !== "undefined" && module !== null) {
    module.exports = Levenshtein;
  }
  // web worker
  else if (typeof self !== "undefined" && typeof self.postMessage === 'function' && typeof self.importScripts === 'function') {
    self.Levenshtein = Levenshtein;
  }
  // browser main thread
  else if (typeof window !== "undefined" && window !== null) {
    window.Levenshtein = Levenshtein;
  }
}());


text:
---
uid: SolidEdgeDraft.DraftPrintUtility.DisplayCutLine
summary:
remarks:
---


text:
(翻译中 by runningwater)
15 JavaScript frameworks and libraries
============================================================
 ![15 JavaScript frameworks and libraries](https://opensource.com/sites/default/files/styles/image-full-size/public/images/life/code_javascript.jpg?itok=a4uULCF0 "15 JavaScript frameworks and libraries")

>Image credits : Photo by Jen Wike Huger

JavaScript is the future.

The language is supported by a number of technololgy leaders, one of whom is WordPress's founder Matt Mullenweg, who hinted that [WordPress developers][18]should learn it, clearly sending a message to the WordPress community as to it future importance. The mention was well received. The transition to better technology will enable WordPress to keep up with future challenges.

JavaScript's open source stance is also one of the best. Contrary to popular belief, JavaScript is not a project, but a specification with an open standard where the language is evolved and maintained by its core team. [ECMAScript][19], another fancy name of JavaScript, is not open source, but it too has an open standard.

You can easily see evidence of JavaScript's popularity when you look at both at GitHub. JavaScript is the top programming language when it comes to the[number of repositories][20]. Its prominance is also evident on Livecoding.tv, where members are diligently creating more videos on JavaScript than any other topic. At the time of this writing, the self-dubbed edutainment site hosts [45,919 JavaScript videos][21].

### Top open source JavaScript frameworks and libraries

Getting back to the topic, JavaScript is blessed with a large community that thrives on improving the technology. Hundreds of JavaScript frameworks and libraries are available to developers, and the good news is that the best ones are open source. For a JavaScript developer, using the best framework or libraries for rapid development is now a necessity. The current market demands rapid development. Also, reinventing the wheel is not a good idea in the current market. Regardless of whether you are new to JavaScript or an experienced JavaScript developer, using libraries and frameworks improves your work significantly.

Let's get started.

### 1\. Angular.js

[Angular.js][1] is one of the most popular JavaScript frameworks. It is used by developers to create complex web apps. The idea behind Angular.js is its one-page app model. It also supports MVC architecture. With Angular.js, the developer can use JavaScript code in the front end, literally extending the HTML vocabulary.

Angular.js has improved a great deal since its inception in 2009\. The current stable version of Angular 1 is 1.5.8/1.2.30\. You can also try out Angular 2, a significant improvement over Angular 1, but this framework is still not yet adopted by developers across the world.

Angular.js uses data binding as one of the main concepts to get work done. The user interacts with the interface. When the interaction is done, the view is then updated with the new values, which in turn interact with the model and ensure everything is synchronized. The DOM gets updated after the underlying logic is executed in the model.

### 2\. Backbone.js

Not everyone intends to build a complex web application. Simpler web application frameworks such as [Backbone.js][2] are a great fit for those learning web app development. Backbone.js is a straightforward framework that makes building simple web apps fun and speedy. Just like Angular.js, Backbone.js also comes with MVC support. Other key features of Backbone.js are routing, RESTful API's support, proper state management, and much more. You can also use Backbone.js to build single page apps.

The current stable version is 1.3.3 and is available from [GitHub][22].

### 3\. D3.js

[D3.js][3] is an excellent JavaScript library that enables developers to create rich web pages with data manipulation features. D3.js uses SVG, HTML, and CSS to make the magic happen. With D3.js, you can bind data to DOM easily and enable data-driven events. With D3.js, you also can create high-quality data-driven web pages that offer a better understanding of data coupled with great visuals. Check[Hamiltonian Graphs from LCF notation][23], powered by D3.js.

### 4\. React.js

[React.js][4] is an interesting JavaScript framework to work with. Unlike other JavaScript frameworks, React.js is ideal for building highly scalable front-end user interfaces. React.js came into existence in 2013 under a BSD license and is growing rapidly thanks to the advantages that it brings to developing complex yet beautiful user interfaces.

The core idea behind React.js is the virtual DOM. Virtual DOM acts as a mediator between the client-side and the server-side, bringing improved performance. The changes made in the virtual DOM are matched with the server DOM, and only the needed elements are updated, making the process much faster than a traditional UI update.

You can also use material design with React, enabling you to develop modern web apps with unparalleled performance.

Check out mittax from Munich, Germany working on React Material-UI in the video below.

### 6\. jQuery

[jQuery][5] is a very popular JavaScript library with features such as event handling, animation, and much more. When working on a web project, you don't want to waste time writing code for simple tasks. jQuery frees you from this with its easy-to-use API. It also works with all the popular web browsers. With jQuery, you can seamlessly control the DOM and also develop an Ajax application, which is in high demand for the last few years. With jQuery, developers don't have to worry about low-level interactions and can easily develop their web applications faster and easier.

jQuery also facilitates the separation of HTML and JavaScript code, enabling developers to write clean code with cross-browser compatibility. Moreover, web apps created using jQuery are easily improved and extended in the future.

### 7\. Ember.js

[Ember.js][6] is a mix of Angular.js and React.js when it comes to functionality. You can easily see the popularity of Ember.js when observing the support community. New features are added constantly. It works similar to Angular.js when it comes to syncing data. The two-way data exchange ensures that the app is fast and scalable. It also helps developers to create front-end elements.

When it comes to React.js for similarities, Ember.js provides similar server-side Virtual DOM for better performance and scalability. Ember.js also encourages minimal code writing, offers excellent APIs to work with, and has an excellent community.

### 8\. Polymer.js

If you ever thought of creating your own HTML5 elements, you can do so with the help of [Polymer.js][7]. Polymer's main focus is to provide extended functionality to web developers by giving them the ability to create their own tags. For example, you can create a <my_video> element with its own functionality that is similar to the <video> element in HTML5.

Polymer was introduced in the year 2013 by Google and is covered under [3-Clause BSD][24].

### 9\. Three.js

[Three.js][8] is yet another JavaScript library, with a focus on 3D development. If you are into animation and game development, you can use Three.js to your advantage. Under the hood, Three.js uses WebGL and can easily be used to render 3D objects on the screen. A popular example of the power of Three.js is HexGL, a futuristic racing game.

### 10\. PhantomJS

Working with JavaScript can also mean working with different browsers, and, when we talk about browsers, resource management comes into the discussion easily. With [PhantomJS][25], you can monitor the performance of your web app thanks to the headless WebKit provided. The headless WebKit is part of the rendering engine used in Chrome and Safari.

The entire process is automated, and all you need to is set up your web application using the available APIs.

### 11\. BabylonJS

[BabylonJS][9] stands in the territory of Three.js, providing JavaScript APIs to create seamless, powerful 3D web apps. It is open source and is based on JavaScript and the power of WebGL. Creating simple 3D objects such as a sphere is easy and you can do so with just a few lines of code. You can get a good grasp of what the library has to offer by going through its [documentation][10]. The homepage also offers excellent demos for inspiration purposes. Do check them out by visiting the official website.

### 12\. Boba.js

Web apps always have one need in common, analytics. If you have struggled to insert analytics into your JavaScript web app, then look to [Boba.js][11]. Boba.js can help you insert analytics into your web app with support for the old ga.js. You can also integrate metrics with Boba.js. The only requirement is jQuery.

### 13\. Underscore.js

[Underscore.js][12] is the answer to your blank HTML editor file. When you start a project, feeling lost or doing a series of steps that repeat what you have done in earlier projects is common. To simplify the process of starting a project and to give you a head start, the Underscore.js JavaScript library provides you with a set of functions. For example, you can use your favorite Backbone.js suspenders or with jQuery functions that you use in your projects frequently.

Functional helpers such as "filter" and "invoke the map" provide you with a good head start so you can dive into your work as quickly as possible. Underscore.js also comes with a suite for easy testing purposes.

### 14\. Meteor.js

[Meteor.js][13] is a fast way to get started building JavaScript apps. It is open source in nature and can be used to create apps for desktop, mobile, and the web. Meteor.js is a full-stack framework and enables end-to-end development for multiple platforms. You can create back-end and front-end features with Meteor.js, and also keep tabs on the performance of the application. The community surrounding Meteor.js is huge, so there are frequent feature and bug fix updates. Meteor.js is also modular in nature and can be equipped with amazing APIs.

### 15\. Knockout.js

[Knockout.js][14] is clearly the most underrated framework out there. It was developed by [Steve Sanderson][15] as an open source JavaScript Framework and is available under license from MIT. The framework is based on the MVVM design.

### Notable Mention: Node.js

[Node.js][16] is a powerful runtime environment for JavaScript. It can be used to build fast, scalable applications with real world data. It is neither a framework nor a library, but a runtime environment that is based on Google Chrome's JavaScript V8 Engine. You can use Node.js to create diversified JavaScript applications, including single page applications, real-time web applications, and much more. Technically, Node.js supports asynchronous I/O with the help of its event-driven architecture. This approach makes it an excellent choice for developing highly scalable solutions. View [Node.js][17] videos on livecoding.tv.

### Conclusion

JavaScript is the lingua franca of the web. It has grown rapidly not only because of what it offers, but also because of the open source community surrounding it. The above-mentioned frameworks and libraries are must-checks for any JavaScript developer. All of them provide some way to explore JavaScript and front-end development. Most of the above-mentioned libraries and frameworks are frequently used on Livecoding.tv by software engineers who are interested in JavaScript and its associated technologies.

If you have something to add, please comment below and let us know. We are eager to see which framework and library you use for projects and also want to know the reason behind it.

--------------------------------------------------------------------------------

via: https://opensource.com/article/16/11/15-javascript-frameworks-libraries

作者:[Dr. Michael J. Garbade  ][a]
译者:[runningwater](https://github.com/runningwater)
校对:[校对者ID](https://github.com/校对者ID)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出

[a]:https://opensource.com/users/drmjg
[1]:https://opensource.com/article/16/11/www.angularjs.org
[2]:https://opensource.com/article/16/11/www.backbone.js
[3]:https://opensource.com/article/16/11/www.d3js.org
[4]:https://facebook.github.io/react/
[5]:http://jquery.com/
[6]:http://emberjs.com/
[7]:https://www.polymer-project.org/1.0/
[8]:https://threejs.org/
[9]:http://www.babylonjs.com/
[10]:https://doc.babylonjs.com/
[11]:http://boba.space150.com/
[12]:http://underscorejs.org/
[13]:https://www.meteor.com/
[14]:http://knockoutjs.com/
[15]:http://blog.stevensanderson.com/
[16]:https://nodejs.org/en/
[17]:https://www.livecoding.tv/learn/node-js/
[18]:http://wesbos.com/learn-javascript/
[19]:http://stackoverflow.com/questions/5520245/is-javascript-an-open-source-project
[20]:https://github.com/blog/2047-language-trends-on-github
[21]:https://www.livecoding.tv/learn/javascript/
[22]:https://github.com/jashkenas/backbone/
[23]:http://christophermanning.org/projects/building-cubic-hamiltonian-graphs-from-lcf-notation
[24]:https://en.wikipedia.org/wiki/BSD_licenses#3-clause
[25]:https://phantomjs.org/