/* * Aphelion * Copyright (c) 2015 Joris van der Wel * * This file is part of Aphelion * * Aphelion is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, version 3 of the License. * * Aphelion is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Aphelion. If not, see <http://www.gnu.org/licenses/>. * * In addition, the following supplemental terms apply, based on section 7 of * the GNU Affero General Public License (version 3): * a) Preservation of all legal notices and author attributions * b) Prohibition of misrepresentation of the origin of this material, and * modified versions are required to be marked in reasonable ways as * different from the original version (for example by appending a copyright notice). * * Linking this library statically or dynamically with other modules is making a * combined work based on this library. Thus, the terms and conditions of the * GNU Affero General Public License cover the whole combination. * * As a special exception, the copyright holders of this library give you * permission to link this library with independent modules to produce an * executable, regardless of the license terms of these independent modules, * and to copy and distribute the resulting executable under terms of your * choice, provided that you also meet, for each linked independent module, * the terms and conditions of the license of that module. An independent * module is a module which is not derived from or based on this library. */ package aphelion.shared.physics.entities; import aphelion.shared.physics.*; import aphelion.shared.physics.valueobjects.PhysicsPoint; import aphelion.shared.physics.valueobjects.PhysicsPointHistory; import aphelion.shared.physics.valueobjects.PhysicsPointHistoryDetailed; import aphelion.shared.physics.valueobjects.PhysicsPositionVector; import org.junit.Before; import org.junit.Test; import java.lang.reflect.Field; import java.util.Iterator; import static org.junit.Assert.*; /** * @author Joris */ public class MapEntityTest { private static final Field trailingStatesField; static { try { trailingStatesField = SimpleEnvironment.class.getDeclaredField("trailingStates"); trailingStatesField.setAccessible(true); } catch (NoSuchFieldException e) { throw new Error(e); } } private EnvironmentConf econf; private SimpleEnvironment env; private State oldState; private State state; private MapEntity[] crossStateList; private MapEntity oldEn; private MapEntity en; private static class MyMapEntity extends MapEntity { public MyMapEntity(State state, MapEntity[] crossStateList, long createdAt_tick, int historyLength) { super(state, crossStateList, createdAt_tick, historyLength); } @Override public void performDeadReckoning(PhysicsMap map, long tick_now, long reckon_ticks, boolean applyForceEmitters) { // noop } } @Before public void setUp() throws IllegalAccessException { econf = new EnvironmentConf(false, true, true); env = new SimpleEnvironment(econf, new MapEmpty(), false); oldState = ( (State[]) trailingStatesField.get(env) )[2]; state = ( (State[]) trailingStatesField.get(env) )[1]; crossStateList = new MapEntity[econf.TRAILING_STATES]; en = new MyMapEntity(state, crossStateList, /* created at tick */ 90, /* history length*/ 10); en.pos.pos.set(1000, 2000); en.pos.vel.set(3, 7); en.updatedPosition(state.tick_now); oldEn = new MyMapEntity(oldState, crossStateList, 90, 10); oldEn.pos.pos.set(5000, 7000); oldEn.pos.vel.set(4, 9); oldEn.updatedPosition(oldState.tick_now); // (the entities are not registered in the state in this test) tickTo(state, 100); } private void tickTo(State target, int tick) { while (target.tick_now < tick) { env.tick(); oldEn.pos.pos.add(oldEn.pos.vel); en.pos.pos.add(en.pos.vel); oldEn.updatedPosition(oldState.tick_now); en.updatedPosition(state.tick_now); } } @Test public void testSoftRemove() { assert crossStateList[state.id] == en; assertFalse(en.isRemovedSet()); assertFalse(en.isRemoved(1)); assertFalse(en.isRemoved(100)); assertFalse(en.isRemoved(1000)); en.softRemove(105); assert crossStateList[state.id] == en; assertTrue(en.isRemovedSet()); assertFalse(en.isRemoved(1)); assertFalse(en.isRemoved(104)); assertTrue(en.isRemoved(105)); assertTrue(en.isRemoved(106)); assertTrue(en.isRemoved(1000)); en.softRemove(104); assert crossStateList[state.id] == en; assertTrue(en.isRemovedSet()); assertFalse(en.isRemoved(1)); assertFalse(en.isRemoved(103)); assertTrue(en.isRemoved(104)); assertTrue(en.isRemoved(105)); assertTrue(en.isRemoved(1000)); } @Test public void testHardRemove() { assert crossStateList[state.id] == en; en.markDirtyPositionPath(95); assert en.dirtyPositionPathLink_state.head != null; assertFalse(en.isRemovedSet()); assertFalse(en.isRemoved(1)); assertFalse(en.isRemoved(100)); assertFalse(en.isRemoved(1000)); en.hardRemove(105); assert crossStateList[state.id] == null; assert en.dirtyPositionPathLink_state.head == null; assertTrue(en.isRemovedSet()); assertFalse(en.isRemoved(1)); assertFalse(en.isRemoved(104)); assertTrue(en.isRemoved(105)); assertTrue(en.isRemoved(106)); assertTrue(en.isRemoved(1000)); en.hardRemove(104); assert crossStateList[state.id] == null; assert en.dirtyPositionPathLink_state.head == null; assertTrue(en.isRemovedSet()); assertFalse(en.isRemoved(1)); assertFalse(en.isRemoved(103)); assertTrue(en.isRemoved(104)); assertTrue(en.isRemoved(105)); assertTrue(en.isRemoved(1000)); } @Test public void testIsNonExistent() { assertTrue(en.isNonExistent(1)); assertTrue(en.isNonExistent(89)); // entity created at tick 90 assertFalse(en.isNonExistent(100)); assertFalse(en.isNonExistent(1000)); en.softRemove(105); assertFalse(en.isNonExistent()); // state tick_now (100) assertTrue(en.isNonExistent(1)); assertTrue(en.isNonExistent(89)); assertFalse(en.isNonExistent(100)); assertFalse(en.isNonExistent(104)); assertTrue(en.isNonExistent(105)); assertTrue(en.isNonExistent(1000)); en.softRemove(100); assertTrue(en.isNonExistent()); // state tick_now (100) } @Test public void testMarkDirtyPositionPath_beforeCreation() { assert en.dirtyPositionPathLink_state.head == null; // entity is created at tick 90 en.markDirtyPositionPath(85); // before its creation tick assertFalse(en.dirtyPositionPathTracker.isDirty(89)); assertTrue(en.dirtyPositionPathTracker.isDirty(90)); assertTrue(en.dirtyPositionPathTracker.isDirty(1000)); assert en.dirtyPositionPathLink_state.head == state.dirtyPositionPathList; } @Test public void testMarkDirtyPositionPath_afterCreation() { assert en.dirtyPositionPathLink_state.head == null; // entity is created at tick 90 en.markDirtyPositionPath(95); // after its creation tick assertFalse(en.dirtyPositionPathTracker.isDirty(94)); assertTrue(en.dirtyPositionPathTracker.isDirty(95)); assertTrue(en.dirtyPositionPathTracker.isDirty(1000)); assert en.dirtyPositionPathLink_state.head == state.dirtyPositionPathList; } @Test public void testGetEntityAtOnlyThis() { // last argument false means do not look at other states assert null == en.getEntityAt(89, false, false); assert en == en.getEntityAt(90, false, false); en.softRemove(105); assert en == en.getEntityAt(104, false, false); assert null == en.getEntityAt(105, false, false); assert en == en.getEntityAt(105, true, false); // ignore soft delete } @Test public void testGetEntityAt() { tickTo(oldState, 100); assert null == en.getEntityAt(89, false, true); assert oldEn == en.getEntityAt(90, false, true); assert oldEn == en.getEntityAt(99, false, true); assert oldEn == en.getEntityAt(100, false, true); assert en == en.getEntityAt(101, false, true); assert null == oldEn.getEntityAt(89, false, true); assert oldEn == oldEn.getEntityAt(90, false, true); assert oldEn == oldEn.getEntityAt(99, false, true); assert oldEn == oldEn.getEntityAt(100, false, true); assert en == oldEn.getEntityAt(101, false, true); tickTo(oldState, 100 + econf.HIGHEST_DELAY); // too far back for the createdAt to interfere assert null == en.getEntityAt(100, false, true); assert null == en.getEntityAt(oldState.tick_now + 1000, false, true); } @Test public void testGetPosition() { final PhysicsPositionVector pos = new PhysicsPositionVector(); en.getPosition(pos); pos.pos.assertEquals(1396, 2924); pos.vel.assertEquals(3, 7); } @Test public void testGetHistoricPositionOnlyThis() { final PhysicsPositionVector pos = new PhysicsPositionVector(); // into the future assertFalse(en.getHistoricPosition(pos, 101, false)); assert !pos.pos.set; assert !pos.vel.set; assertTrue(en.getHistoricPosition(pos, 100, false)); pos.pos.assertEquals(1396, 2924); pos.vel.assertEquals(3, 7); assertTrue(en.getHistoricPosition(pos, 99, false)); pos.pos.assertEquals(1393, 2917); pos.vel.assertEquals(3, 7); assertTrue(en.getHistoricPosition(pos, 91, false)); pos.pos.assertEquals(1369, 2861); pos.vel.assertEquals(3, 7); //further back than the history length we set (10) assertFalse(en.getHistoricPosition(pos, 90, false)); assert !pos.pos.set; assert !pos.vel.set; } @Test public void testGetHistoricPosition() { final PhysicsPositionVector pos = new PhysicsPositionVector(); tickTo(oldState, 100); // into the future assertFalse(en.getHistoricPosition(pos, state.tick_now + 1, true)); assert !pos.pos.set; assert !pos.vel.set; assertTrue(en.getHistoricPosition(pos, state.tick_now, true)); pos.pos.assertEquals(1492, 3148); pos.vel.assertEquals(3, 7); assertTrue(en.getHistoricPosition(pos, state.tick_now - 1, true)); pos.pos.assertEquals(1489, 3141); pos.vel.assertEquals(3, 7); // the older entity has a different position on purpose so that we can test this assertTrue(en.getHistoricPosition(pos, oldState.tick_now, true)); pos.pos.assertEquals(5656, 8476); pos.vel.assertEquals(4, 9); } @Test public void testGetHistoricSmoothPosition() { // Not implemented in MapEntities by default final PhysicsPoint pos = new PhysicsPoint(); assertFalse(en.getHistoricSmoothPosition(pos, state.tick_now, false)); assert !pos.set; } @Test public void testUpdatedPositionFutureTick() { assertTrue(en.dirtyPositionPathTracker.isDirty(101)); en.pos.pos.set(500000, 666666); en.pos.vel.set(-1, -2); en.updatedPosition(101); assertEquals(500000, en.posHistory.getX(101)); assertEquals(666666, en.posHistory.getY(101)); assertEquals(-1, en.velHistory.getX(101)); assertEquals(-2, en.velHistory.getY(101)); assertFalse(en.dirtyPositionPathTracker.isDirty(101)); // 101 is not the current tick so not in the entity grid: Iterator<MapEntity> it = state.entityGrid.iterator(new PhysicsPoint(500000, 666666), 1); assertFalse(it.hasNext()); } @Test public void testUpdatedPositionNow() { en.pos.pos.set(500000, 666666); en.pos.vel.set(-1, -2); en.updatedPosition(100); assertEquals(500000, en.posHistory.getX(100)); assertEquals(666666, en.posHistory.getY(100)); assertEquals(-1, en.velHistory.getX(100)); assertEquals(-2, en.velHistory.getY(100)); // 100 is the current tick so we should be in the grid Iterator<MapEntity> it = state.entityGrid.iterator(new PhysicsPoint(500000, 666666), 1); assertTrue(it.hasNext()); assertEquals(it.next(), en); } @Test public void testUpdatedPositionNowRemoved() { en.softRemove(100); en.pos.pos.set(500000, 666666); en.pos.vel.set(-1, -2); en.updatedPosition(100); assertEquals(500000, en.posHistory.getX(100)); assertEquals(666666, en.posHistory.getY(100)); assertEquals(-1, en.velHistory.getX(100)); assertEquals(-2, en.velHistory.getY(100)); // 100 is the current tick so we should be in the grid // if removed we should not be in the entity grid Iterator<MapEntity> it = state.entityGrid.iterator(new PhysicsPoint(500000, 666666), 1); assertFalse(it.hasNext()); } @Test public void testGetAndSeekHistoryDetail() { final PhysicsPoint pos = new PhysicsPoint(); en.posHistory.appendDetail(98, 1100, 2005); PhysicsPointHistoryDetailed history = en.getAndSeekHistoryDetail(98, false); assertTrue(history.hasNextDetail()); history.nextDetail(pos); pos.assertEquals(1100, 2005); assertFalse(history.hasNextDetail()); // history length was set at "10" assertNull(en.getAndSeekHistoryDetail(89, false)); } @Test public void testReset() { tickTo(oldState, 100); assertEquals(101, oldEn.dirtyPositionPathTracker.getFirstDirtyTick()); oldEn.createdAt_tick = 89; oldEn.softRemove(110); oldEn.pos.pos.set(1234, 5678); oldEn.pos.vel.set(910, 1112); oldEn.updatedPosition(oldState.tick_now); oldEn.markDirtyPositionPath(95); assertEquals(100, oldEn.posHistory.getHighestTick()); assertEquals(95, oldEn.dirtyPositionPathTracker.getFirstDirtyTick()); en.resetTo(oldEn); assertEquals(95, en.dirtyPositionPathTracker.getFirstDirtyTick()); assertEquals(89, en.createdAt_tick); assertEquals(110, en.removedAt_tick); assertTrue(en.isRemovedSet()); en.pos.pos.assertEquals(1234, 5678); en.pos.vel.assertEquals(910, 1112); assertEquals(100, en.posHistory.getHighestTick()); assertEquals(1234, en.posHistory.getX(100)); assertEquals(5678, en.posHistory.getY(100)); assertEquals(100, en.velHistory.getHighestTick()); assertEquals(910, en.velHistory.getX(100)); assertEquals(1112, en.velHistory.getY(100)); assertTrue(en.dirtyPositionPathTracker.isDirty(95)); assertFalse(en.dirtyPositionPathTracker.isDirty(94)); } @Test public void testResetForeign() throws IllegalAccessException { // test a foreign reset with a different history length // First environment // econf = new EnvironmentConf(false, true, true); env = new SimpleEnvironment(econf, new MapEmpty(), false); oldState = ( (State[]) trailingStatesField.get(env) )[1]; state = ( (State[]) trailingStatesField.get(env) )[0]; crossStateList = new MapEntity[econf.TRAILING_STATES]; // overlapping histories (by 5 ticks) en = new MyMapEntity(state, crossStateList, 2, econf.TRAILING_STATE_DELAY + 5); en.pos.pos.set(1000, 2000); en.pos.vel.set(3, 7); en.updatedPosition(state.tick_now); oldEn = new MyMapEntity(oldState, crossStateList, 0, econf.TRAILING_STATE_DELAY + 5); oldEn.pos.pos.set(5000, 7000); oldEn.pos.vel.set(4, 9); oldEn.updatedPosition(oldState.tick_now); // The foreign environment // EnvironmentConf econfForeign = new EnvironmentConf(false, true, true); SimpleEnvironment envForeign = new SimpleEnvironment(econfForeign, new MapEmpty(), false); State foreignState = ( (State[]) trailingStatesField.get(envForeign) )[0]; MapEntity entityForeign = new MyMapEntity(foreignState, new MapEntity[1], 90, econf.TRAILING_STATE_DELAY * 2); entityForeign.pos.pos.set(500000, 400000); entityForeign.pos.vel.set(150, 100); entityForeign.updatedPosition(foreignState.tick_now); assertNotEquals(en.HISTORY_LENGTH, entityForeign.HISTORY_LENGTH); while (state.tick_now < 100) { env.tick(); envForeign.tick(); assert env.getTick() == envForeign.getTick(); oldEn.pos.pos.add(oldEn.pos.vel); en.pos.pos.add(en.pos.vel); entityForeign.pos.pos.add(entityForeign.pos.vel); oldEn.updatedPosition(oldState.tick_now); en.updatedPosition(state.tick_now); entityForeign.updatedPosition(foreignState.tick_now); } // Now at tick 100 assertEquals(101, en.dirtyPositionPathTracker.getFirstDirtyTick()); en.createdAt_tick = 89; en.softRemove(110); en.pos.pos.set(1234, 5678); en.pos.vel.set(910, 1112); en.updatedPosition(state.tick_now); en.markDirtyPositionPath(95); assertEquals(100, en.posHistory.getHighestTick()); assertEquals(95, en.dirtyPositionPathTracker.getFirstDirtyTick()); entityForeign.resetTo(en); assertEquals(95, entityForeign.dirtyPositionPathTracker.getFirstDirtyTick()); assertEquals(89, entityForeign.createdAt_tick); assertEquals(110, entityForeign.removedAt_tick); assertTrue(entityForeign.isRemovedSet()); entityForeign.pos.pos.assertEquals(1234, 5678); entityForeign.pos.vel.assertEquals(910, 1112); assertEquals(100, entityForeign.posHistory.getHighestTick()); assertEquals(1234, entityForeign.posHistory.getX(100)); assertEquals(5678, entityForeign.posHistory.getY(100)); assertEquals(100, entityForeign.velHistory.getHighestTick()); assertEquals(910, entityForeign.velHistory.getX(100)); assertEquals(1112, entityForeign.velHistory.getY(100)); assertTrue(entityForeign.dirtyPositionPathTracker.isDirty(95)); assertFalse(entityForeign.dirtyPositionPathTracker.isDirty(94)); assertHistoryTickEqual(en.posHistory, entityForeign.posHistory, 99); assertHistoryTickEqual(en.posHistory, entityForeign.posHistory, 100 - econf.TRAILING_STATE_DELAY); // This is where state 0 and 1 of "env" overlap by 5 ticks; state 0 has priority: assertHistoryTickEqual(en.posHistory, entityForeign.posHistory, 100 - econf.TRAILING_STATE_DELAY - 4); assertHistoryTickEqual(oldEn.posHistory, entityForeign.posHistory, 100 - econf.TRAILING_STATE_DELAY - 5); assertHistoryTickEqual(oldEn.posHistory, entityForeign.posHistory, 100 - econf.TRAILING_STATE_DELAY * 2 + 1); assertHistoryTickEqual(en.velHistory, entityForeign.velHistory, 99); assertHistoryTickEqual(en.velHistory, entityForeign.velHistory, 100 - econf.TRAILING_STATE_DELAY); // This is where state 0 and 1 of "env" overlap by 5 ticks; state 0 has priority: assertHistoryTickEqual(en.velHistory, entityForeign.velHistory, 100 - econf.TRAILING_STATE_DELAY - 4); assertHistoryTickEqual(oldEn.velHistory, entityForeign.velHistory, 100 - econf.TRAILING_STATE_DELAY - 5); assertHistoryTickEqual(oldEn.velHistory, entityForeign.velHistory, 100 - econf.TRAILING_STATE_DELAY * 2 + 1); } private static void assertHistoryTickEqual(PhysicsPointHistory expected, PhysicsPointHistory actual, long tick) { assertEquals(expected.getX(tick), actual.getX(tick)); assertEquals(expected.getY(tick), actual.getY(tick)); } }