/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.tests; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.tests.utils.GdxTest; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.PerformanceCounter; import java.util.Comparator; /** For testing and benchmarking of gdx.utils.Select and its associated algorithms/classes * @author Jon Renner */ public class SelectTest extends GdxTest { static PerformanceCounter perf = new PerformanceCounter("bench"); static boolean verify; // verify and report the results of each selection private static boolean quiet; @Override public void create () { int n = 100; player = createDummies(n); enemy = createDummies(n); int runs = 100; // run correctness first to warm up the JIT and other black magic quiet = true; allRandom(); print("VERIFY CORRECTNESS FIND LOWEST RANKED"); correctnessTest(runs, 1); print("VERIFY CORRECTNESS FIND MIDDLE RANKED"); correctnessTest(runs, enemy.size / 2); print("VERIFY CORRECTNESS FIND HIGHEST RANKED"); correctnessTest(runs, enemy.size); runs = 1000; quiet = true; print("BENCHMARK FIND LOWEST RANKED"); performanceTest(runs, 1); print("BENCHMARK FIND MIDDLE RANKED"); performanceTest(runs, enemy.size / 2); print("BENCHMARK FIND HIGHEST RANKED"); performanceTest(runs, enemy.size); print("TEST CONSISTENCY FOR LOWEST RANKED"); consistencyTest(runs, 1); print("TEST CONSISTENCY FOR MIDDLE RANKED"); consistencyTest(runs, enemy.size / 2); print("TEST CONSISTENCY FOR HIGHEST RANKED"); consistencyTest(runs, enemy.size); // test that selectRanked and selectRankedIndex return the same print("TEST selectRanked AND selectRankedIndex RETURN MATCHING RESULTS - LOWEST RANKED"); testValueMatchesIndex(runs, 1); print("TEST selectRanked AND selectRankedIndex RETURN MATCHING RESULTS - MIDDLE RANKED"); testValueMatchesIndex(runs, enemy.size / 2); print("TEST selectRanked AND selectRankedIndex RETURN MATCHING RESULTS - HIGHEST RANKED"); testValueMatchesIndex(runs, enemy.size); print("ALL TESTS PASSED"); } public static void correctnessTest (int runs, int k) { String msg = String.format("[%d runs with %dx%d dummy game units] - ", runs, player.size, enemy.size); verify = true; test(runs, k); print(msg + "VERIFIED"); } public static void performanceTest (int runs, int k) { verify = false; test(runs, k); String msg = String.format("[%d runs with %dx%d dummy game units] - ", runs, player.size, enemy.size); print(msg + String.format("avg: %.5f, min/max: %.4f/%.4f, total time: %.3f (ms), made %d comparisons", allPerf.time.min, allPerf.time.max, allPerf.time.average * 1000, allPerf.time.total * 1000, comparisonsMade)); } public static void consistencyTest (int runs, int k) { verify = false; Dummy test = player.get(0); Dummy lastFound = null; allRandom(); for (int i = 0; i < runs; i++) { Dummy found = test.getKthNearestEnemy(k); if (lastFound == null) { lastFound = found; } else { if (!(lastFound.equals(found))) { print("CONSISTENCY TEST FAILED"); print("lastFound: " + lastFound); print("justFound: " + found); throw new GdxRuntimeException("test failed"); } } } } public static void testValueMatchesIndex (int runs, int k) { verify = false; for (int i = 0; i < runs; i++) { allRandom(); player.shuffle(); enemy.shuffle(); originDummy = player.random(); int idx = enemy.selectRankedIndex(distComp, k); Dummy indexDummy = enemy.get(idx); Dummy valueDummy = enemy.selectRanked(distComp, k); if (!(indexDummy.equals(valueDummy))) { throw new GdxRuntimeException("results of selectRankedIndex and selectRanked do not return the same object\n" + "selectRankedIndex -> " + indexDummy + "\n" + "selectRanked -> " + valueDummy); } } } public static void test (int runs, int k) { // k = kth order statistic comparisonsMade = 0; perf.reset(); allPerf.reset(); allRandom(); enemy.shuffle(); player.shuffle(); for (int i = 0; i < runs; i++) { getKthNearestEnemy(quiet, k); } } public static void allRandom () { for (Dummy d : player) { d.setRandomPos(); } for (Dummy d : enemy) { d.setRandomPos(); } } private static PerformanceCounter allPerf = new PerformanceCounter("all"); public static void getKthNearestEnemy (boolean silent, int k) { Dummy kthDummy = null; perf.reset(); allPerf.start(); for (Dummy d : player) { Dummy found = d.getKthNearestEnemy(k); } allPerf.stop(); allPerf.tick(); if (silent) return; print(String.format("found nearest. min: %.4f, max: %.4f, avg: %.4f, total: %.3f ms", perf.time.min * 1000, perf.time.max * 1000, perf.time.average * 1000, perf.time.total * 1000)); } public static void verifyCorrectness (Dummy d, int k) { enemy.sort(distComp); int idx = enemy.indexOf(d, true); // remember that k = min value = 0 position in the array, therefore k - 1 if (enemy.get(idx) != enemy.get(k - 1)) { System.out.println("origin dummy: " + originDummy); System.out.println("TEST FAILURE: " + "idx: " + idx + " does not equal (k - 1): " + (k - 1)); throw new GdxRuntimeException("test failed"); } } static class Dummy { public Vector2 pos; public int id; public Dummy () { // set the position manually } @Override public boolean equals (Object obj) { if (!(obj instanceof Dummy)) { throw new GdxRuntimeException("do not compare to anything but other Dummy objects"); } Dummy d = (Dummy)obj; // we only care about position/distance float epsilon = 0.0001f; float diff = Math.abs(d.pos.x - this.pos.x) + Math.abs(d.pos.y - this.pos.y); if (diff > epsilon) return false; return true; } public Dummy getKthNearestEnemy (int k) { perf.start(); originDummy = this; Dummy found = enemy.selectRanked(distComp, k); // print(this + " found enemy: " + found); perf.stop(); perf.tick(); if (verify) { verifyCorrectness(found, k); } return found; } public void setRandomPos () { float max = 100; this.pos.x = -max + MathUtils.random(max * 2); this.pos.y = -max + MathUtils.random(max * 2); float xShift = 100; if (player.contains(this, true)) { this.pos.x -= xShift; } else if (enemy.contains(this, true)) { this.pos.x += xShift; } else { throw new RuntimeException("unhandled"); } } @Override public String toString () { return String.format("Dummy at: %.2f, %.2f", pos.x, pos.y); } } public static int nextID = 1; public static Array<Dummy> player; public static Array<Dummy> enemy; public static Array<Dummy> createDummies (int n) { float variance = 20; Array<Dummy> dummies = new Array<Dummy>(); for (int i = 0; i < n; i++) { Dummy d = new Dummy(); dummies.add(d); d.pos = new Vector2(); d.id = nextID++; } return dummies; } static Dummy originDummy; static long comparisonsMade = 0; static Comparator<Dummy> distComp = new Comparator<Dummy>() { @Override public int compare (Dummy o1, Dummy o2) { comparisonsMade++; float d1 = originDummy.pos.dst2(o1.pos); float d2 = originDummy.pos.dst2(o2.pos); float diff = d1 - d2; if (diff < 0) return -1; if (diff > 0) return 1; return 0; } }; public static void print (Object... objs) { for (Object o : objs) { System.out.print(o); } System.out.println(); } }