diff --git a/core/src/mindustry/ai/BlockIndexer.java b/core/src/mindustry/ai/BlockIndexer.java index ffc8ae6f78..f79fc74b91 100644 --- a/core/src/mindustry/ai/BlockIndexer.java +++ b/core/src/mindustry/ai/BlockIndexer.java @@ -4,6 +4,7 @@ import arc.*; import arc.func.*; import arc.math.*; import arc.math.geom.*; +import arc.struct.EnumSet; import arc.struct.*; import arc.util.*; import mindustry.content.*; @@ -15,6 +16,8 @@ import mindustry.world.*; import mindustry.world.blocks.*; import mindustry.world.meta.*; +import java.util.*; + import static mindustry.Vars.*; /** Class used for indexing special target blocks for AI. */ @@ -28,22 +31,21 @@ public class BlockIndexer{ private final IntSet intSet = new IntSet(); private final ObjectSet itemSet = new ObjectSet<>(); /** Stores all ore quadtrants on the map. */ - private ObjectMap> ores = new ObjectMap<>(); + private ObjectMap ores = new ObjectMap<>(); /** Maps each team ID to a quarant. A quadrant is a grid of bits, where each bit is set if and only if there is a block of that team in that quadrant. */ private GridBits[] structQuadrants; /** Stores all damaged tile entities by team. */ - private ObjectSet[] damagedTiles = new ObjectSet[Team.all().length]; + private TileArray[] damagedTiles = new TileArray[Team.all().length]; /**All ores available on this map.*/ private ObjectSet allOres = new ObjectSet<>(); /**Stores teams that are present here as tiles.*/ - private ObjectSet activeTeams = new ObjectSet<>(); - + private Array activeTeams = new Array<>(); /** Maps teams to a map of flagged tiles by type. */ - private ObjectSet[][] flagMap = new ObjectSet[Team.all().length][BlockFlag.all.length]; + private TileArray[][] flagMap = new TileArray[Team.all().length][BlockFlag.all.length]; /** Maps tile positions to their last known tile index data. */ private IntMap typeMap = new IntMap<>(); /** Empty set used for returning. */ - private ObjectSet emptySet = new ObjectSet<>(); + private TileArray emptySet = new TileArray(); /** Array used for returning and reusing. */ private Array returnArray = new Array<>(); @@ -62,12 +64,12 @@ public class BlockIndexer{ Events.on(WorldLoadEvent.class, event -> { scanOres.clear(); scanOres.addAll(Item.getAllOres()); - damagedTiles = new ObjectSet[Team.all().length]; - flagMap = new ObjectSet[Team.all().length][BlockFlag.all.length]; + damagedTiles = new TileArray[Team.all().length]; + flagMap = new TileArray[Team.all().length][BlockFlag.all.length]; for(int i = 0; i < flagMap.length; i++){ for(int j = 0; j < BlockFlag.all.length; j++){ - flagMap[i][j] = new ObjectSet<>(); + flagMap[i][j] = new TileArray(); } } @@ -98,7 +100,7 @@ public class BlockIndexer{ }); } - private ObjectSet[] getFlagged(Team team){ + private TileArray[] getFlagged(Team team){ return flagMap[team.id]; } @@ -130,14 +132,14 @@ public class BlockIndexer{ } /** Returns all damaged tiles by team. */ - public ObjectSet getDamaged(Team team){ + public TileArray getDamaged(Team team){ returnArray.clear(); if(damagedTiles[team.id] == null){ - damagedTiles[team.id] = new ObjectSet<>(); + damagedTiles[team.id] = new TileArray(); } - ObjectSet set = damagedTiles[team.id]; + TileArray set = damagedTiles[team.id]; for(Tile tile : set){ if((tile.entity == null || tile.entity.team() != team || !tile.entity.damaged()) || tile.block() instanceof BuildBlock){ returnArray.add(tile); @@ -152,7 +154,7 @@ public class BlockIndexer{ } /** Get all allied blocks with a flag. */ - public ObjectSet getAllied(Team team, BlockFlag type){ + public TileArray getAllied(Team team, BlockFlag type){ return flagMap[team.id][type.ordinal()]; } @@ -194,7 +196,7 @@ public class BlockIndexer{ returnArray.clear(); for(Team enemy : team.enemies()){ if(state.teams.isActive(enemy)){ - ObjectSet set = getFlagged(enemy)[type.ordinal()]; + TileArray set = getFlagged(enemy)[type.ordinal()]; if(set != null){ for(Tile tile : set){ returnArray.add(tile); @@ -207,10 +209,10 @@ public class BlockIndexer{ public void notifyTileDamaged(Tilec entity){ if(damagedTiles[(int)entity.team().id] == null){ - damagedTiles[(int)entity.team().id] = new ObjectSet<>(); + damagedTiles[(int)entity.team().id] = new TileArray(); } - ObjectSet set = damagedTiles[(int)entity.team().id]; + TileArray set = damagedTiles[(int)entity.team().id]; set.add(entity.tile()); } @@ -273,7 +275,7 @@ public class BlockIndexer{ * each tile will at least have an ore within {@link #quadrantSize} / 2 blocks of it. * Only specific ore types are scanned. See {@link #scanOres}. */ - public ObjectSet getOrePositions(Item item){ + public TileArray getOrePositions(Item item){ return ores.get(item, emptySet); } @@ -297,11 +299,11 @@ public class BlockIndexer{ private void process(Tile tile){ if(tile.block().flags.size() > 0 && tile.team() != Team.derelict){ - ObjectSet[] map = getFlagged(tile.team()); + TileArray[] map = getFlagged(tile.team()); for(BlockFlag flag : tile.block().flags){ - ObjectSet arr = map[flag.ordinal()]; + TileArray arr = map[flag.ordinal()]; arr.add(tile); @@ -309,7 +311,9 @@ public class BlockIndexer{ } typeMap.put(tile.pos(), new TileIndex(tile.block().flags, tile.team())); } - activeTeams.add(tile.team()); + if(!activeTeams.contains(tile.team())){ + activeTeams.add(tile.team()); + } if(ores == null) return; @@ -331,7 +335,7 @@ public class BlockIndexer{ //update quadrant at this position for(Item item : scanOres){ - ObjectSet set = ores.get(item); + TileArray set = ores.get(item); //update quadrant status depending on whether the item is in it if(!itemSet.contains(item)){ @@ -391,7 +395,7 @@ public class BlockIndexer{ //initialize ore map with empty sets for(Item item : scanOres){ - ores.put(item, new ObjectSet<>()); + ores.put(item, new TileArray()); } for(Tile tile : world.tiles){ @@ -417,4 +421,34 @@ public class BlockIndexer{ this.team = team; } } + + public static class TileArray implements Iterable{ + private Array tiles = new Array<>(false, 16); + private IntSet contained = new IntSet(); + + public void add(Tile tile){ + if(contained.add(tile.pos())){ + tiles.add(tile); + } + } + + public void remove(Tile tile){ + if(contained.remove(tile.pos())){ + tiles.remove(tile); + } + } + + public int size(){ + return tiles.size; + } + + public Tile first(){ + return tiles.first(); + } + + @Override + public Iterator iterator(){ + return tiles.iterator(); + } + } } diff --git a/core/src/mindustry/ai/NewBlockIndexer.java b/core/src/mindustry/ai/NewBlockIndexer.java new file mode 100644 index 0000000000..bd1dc00f78 --- /dev/null +++ b/core/src/mindustry/ai/NewBlockIndexer.java @@ -0,0 +1,50 @@ +package mindustry.ai; + +//new indexer implementation, uses quadtrees +public class NewBlockIndexer{ + + /* + public ObjectSet getOrePositions(Item item){ + + } + + public Tile findClosestOre(float xp, float yp, Item item){ + + } + + public ObjectSet getDamaged(Team team){ + + } + + public ObjectSet getAllied(Team team, BlockFlag type){ + + } + + public boolean eachBlock(Teamc team, float range, Boolf pred, Cons cons){ + return eachBlock(team.team(), team.getX(), team.getY(), range, pred, cons); + } + + public boolean eachBlock(Team team, float wx, float wy, float range, Boolf pred, Cons cons){ + + } + + public Array getEnemy(Team team, BlockFlag type){ + + } + + public void notifyTileDamaged(Tilec entity){ + + } + + public Tilec findEnemyTile(Team team, float x, float y, float range, Boolf pred){ + + } + + public Tilec findTile(Team team, float x, float y, float range, Boolf pred, boolean usePriority){ + + } + + public Tilec findTile(Team team, float x, float y, float range, Boolf pred){ + return findTile(team, x, y, range, pred, false); + }*/ +} diff --git a/core/src/mindustry/entities/def/TileComp.java b/core/src/mindustry/entities/def/TileComp.java index a9a3e3e632..cafd91fcdc 100644 --- a/core/src/mindustry/entities/def/TileComp.java +++ b/core/src/mindustry/entities/def/TileComp.java @@ -8,6 +8,7 @@ import arc.graphics.*; import arc.graphics.g2d.*; import arc.math.*; import arc.math.geom.*; +import arc.math.geom.QuadTree.*; import arc.scene.ui.layout.*; import arc.struct.*; import arc.util.*; @@ -34,7 +35,7 @@ import static mindustry.Vars.*; @EntityDef(value = {Tilec.class}, isFinal = false, genio = false, serialize = false) @Component -abstract class TileComp implements Posc, Teamc, Healthc, Tilec, Timerc{ +abstract class TileComp implements Posc, Teamc, Healthc, Tilec, Timerc, QuadTreeObject{ //region vars and initialization static final float timeToSleep = 60f * 1; static final ObjectSet tmpTiles = new ObjectSet<>(); @@ -996,5 +997,10 @@ abstract class TileComp implements Posc, Teamc, Healthc, Tilec, Timerc{ updateFlow = false; } + @Override + public void hitbox(Rect out){ + out.setCentered(x, y, block.size * tilesize, block.size * tilesize); + } + //endregion } diff --git a/core/src/mindustry/game/Team.java b/core/src/mindustry/game/Team.java index 0e5d60fadc..7d74ed5a60 100644 --- a/core/src/mindustry/game/Team.java +++ b/core/src/mindustry/game/Team.java @@ -39,7 +39,7 @@ public class Team implements Comparable{ } public static Team get(int id){ - return all[Pack.u((byte)id)]; + return all[((byte)id) & 0xff]; } /** @return the 6 base team colors. */ diff --git a/core/src/mindustry/world/Tile.java b/core/src/mindustry/world/Tile.java index d1f0262721..6becdb8b3d 100644 --- a/core/src/mindustry/world/Tile.java +++ b/core/src/mindustry/world/Tile.java @@ -3,6 +3,7 @@ package mindustry.world; import arc.func.*; import arc.math.*; import arc.math.geom.*; +import arc.math.geom.QuadTree.*; import arc.struct.*; import arc.util.ArcAnnotate.*; import mindustry.annotations.Annotations.*; @@ -15,7 +16,7 @@ import mindustry.world.modules.*; import static mindustry.Vars.*; -public class Tile implements Position{ +public class Tile implements Position, QuadTreeObject{ /** Tile traversal cost. */ public byte cost = 1; /** Tile entity, usually null. */ @@ -377,7 +378,12 @@ public class Tile implements Position{ } public Rect getHitbox(Rect rect){ - return rect.setSize(block().size * tilesize).setCenter(drawx(), drawy()); + return rect.setCentered(drawx(), drawy(), block.size * tilesize, block.size * tilesize); + } + + @Override + public void hitbox(Rect rect){ + getHitbox(rect); } public Tile getNearby(Point2 relative){ diff --git a/core/src/mindustry/world/blocks/units/CommandCenter.java b/core/src/mindustry/world/blocks/units/CommandCenter.java index 7c83fbdfb6..f3e24a5927 100644 --- a/core/src/mindustry/world/blocks/units/CommandCenter.java +++ b/core/src/mindustry/world/blocks/units/CommandCenter.java @@ -9,6 +9,7 @@ import arc.scene.ui.layout.*; import arc.struct.*; import arc.util.*; import arc.util.io.*; +import mindustry.ai.BlockIndexer.*; import mindustry.content.*; import mindustry.entities.*; import mindustry.entities.units.*; @@ -94,9 +95,9 @@ public class CommandCenter extends Block{ @Override public void placed(){ super.placed(); - ObjectSet set = indexer.getAllied(team, BlockFlag.comandCenter); + TileArray set = indexer.getAllied(team, BlockFlag.comandCenter); - if(set.size > 0){ + if(set.size() > 0){ CommandCenterEntity oe = set.first().ent(); command = oe.command; } @@ -106,9 +107,9 @@ public class CommandCenter extends Block{ public void onRemoved(){ super.onRemoved(); - ObjectSet set = indexer.getAllied(team, BlockFlag.comandCenter); + TileArray set = indexer.getAllied(team, BlockFlag.comandCenter); - if(set.size == 1){ + if(set.size() == 1){ Groups.unit.each(t -> t.team() == team, u -> u.controller().command(UnitCommand.all[0])); } } diff --git a/tests/src/test/java/ApplicationTests.java b/tests/src/test/java/ApplicationTests.java index 2c0607c38b..2f736c008a 100644 --- a/tests/src/test/java/ApplicationTests.java +++ b/tests/src/test/java/ApplicationTests.java @@ -1,5 +1,7 @@ import arc.*; import arc.backend.headless.*; +import arc.func.*; +import arc.math.*; import arc.math.geom.*; import arc.struct.*; import arc.util.*; @@ -277,6 +279,72 @@ public class ApplicationTests{ world.tile(0, 0).entity.acceptStack(Items.copper, 1000, null); } + @Test + void indexingBasic(){ + resetWorld(); + SaveIO.load(Core.files.internal("77.msav")); + + //test basic method. + Rand r = new Rand(0); + Tilec[] res = {null}; + + Cons assigner = t -> res[0] = t; + + int iterations = 100; + + r.setSeed(0); + + //warmup. + for(int i = 0; i < iterations; i++){ + int x = r.random(0, world.width()), y = r.random(0, world.height()); + float range = r.random(tilesize * 30); + + indexer.eachBlock(Team.sharded, x * tilesize, y * tilesize, range, t -> true, assigner); + } + + //TODO impl + /* + r.setSeed(0); + + for(int i = 0; i < iterations; i++){ + int x = r.random(0, world.width()), y = r.random(0, world.height()); + float range = r.random(tilesize * 30); + + indexer.eachBlock2(Team.sharded, x * tilesize, y * tilesize, range, t -> true, assigner); + }*/ + + //benchmark. + + r.setSeed(0); + + Time.mark(); + + for(int i = 0; i < iterations; i++){ + int x = r.random(0, world.width()), y = r.random(0, world.height()); + float range = r.random(tilesize * 30); + + indexer.eachBlock(Team.sharded, x * tilesize, y * tilesize, range, t -> true, assigner); + } + + Log.info("Time for basic indexing: {0}", Time.elapsed()); + + r.setSeed(0); + + /* + Time.mark(); + + for(int i = 0; i < iterations; i++){ + int x = r.random(0, world.width()), y = r.random(0, world.height()); + float range = r.random(tilesize * 30); + + indexer.eachBlock2(Team.sharded, x * tilesize, y * tilesize, range, t -> true, assigner); + } + + Log.info("Time for quad: {0}", Time.elapsed()); + */ + + } + @Test void conveyorBench(){ int[] itemsa = {0};