Merge branch 'multichunk'
This commit is contained in:
		| @@ -3,6 +3,7 @@ package ru.windcorp.progressia.client; | ||||
| import ru.windcorp.progressia.client.comms.DefaultClientCommsListener; | ||||
| import ru.windcorp.progressia.client.comms.ServerCommsChannel; | ||||
| import ru.windcorp.progressia.client.graphics.world.Camera; | ||||
| import ru.windcorp.progressia.client.graphics.world.EntityAnchor; | ||||
| import ru.windcorp.progressia.client.graphics.world.LocalPlayer; | ||||
| import ru.windcorp.progressia.client.world.WorldRender; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
| @@ -11,14 +12,14 @@ import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
| public class Client { | ||||
| 	 | ||||
| 	private final WorldRender world; | ||||
| 	private LocalPlayer localPlayer; | ||||
| 	private final LocalPlayer localPlayer = new LocalPlayer(this); | ||||
| 	 | ||||
| 	private final Camera camera = new Camera((float) Math.toRadians(70)); | ||||
| 	 | ||||
| 	private final ServerCommsChannel comms; | ||||
| 	 | ||||
| 	public Client(WorldData world, ServerCommsChannel comms) { | ||||
| 		this.world = new WorldRender(world); | ||||
| 		this.world = new WorldRender(world, this); | ||||
| 		this.comms = comms; | ||||
| 		 | ||||
| 		comms.addListener(new DefaultClientCommsListener(this)); | ||||
| @@ -32,8 +33,8 @@ public class Client { | ||||
| 		return localPlayer; | ||||
| 	} | ||||
| 	 | ||||
| 	public void setLocalPlayer(EntityData localPlayer) { | ||||
| 		this.localPlayer = new LocalPlayer(localPlayer); | ||||
| 	public boolean isReady() { | ||||
| 		return localPlayer.hasEntity(); | ||||
| 	} | ||||
| 	 | ||||
| 	public Camera getCamera() { | ||||
| @@ -44,4 +45,15 @@ public class Client { | ||||
| 		return comms; | ||||
| 	} | ||||
|  | ||||
| 	public void onLocalPlayerEntityChanged(EntityData entity, EntityData lastKnownEntity) { | ||||
| 		if (entity == null) { | ||||
| 			getCamera().setAnchor(null); | ||||
| 			return; | ||||
| 		} | ||||
| 		 | ||||
| 		getCamera().setAnchor(new EntityAnchor( | ||||
| 				getWorld().getEntityRenderable(entity) | ||||
| 		)); | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -2,15 +2,12 @@ package ru.windcorp.progressia.client.comms; | ||||
|  | ||||
| import java.io.IOException; | ||||
|  | ||||
| import ru.windcorp.jputil.chars.StringUtil; | ||||
| import ru.windcorp.progressia.client.Client; | ||||
| import ru.windcorp.progressia.client.graphics.world.EntityAnchor; | ||||
| import ru.windcorp.progressia.common.comms.CommsListener; | ||||
| import ru.windcorp.progressia.common.comms.packets.Packet; | ||||
| import ru.windcorp.progressia.common.comms.packets.PacketSetLocalPlayer; | ||||
| import ru.windcorp.progressia.common.comms.packets.PacketWorldChange; | ||||
| import ru.windcorp.progressia.common.util.crash.CrashReports; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
| import ru.windcorp.progressia.common.world.PacketSetLocalPlayer; | ||||
| import ru.windcorp.progressia.common.world.PacketWorldChange; | ||||
|  | ||||
| // TODO refactor with no mercy | ||||
| public class DefaultClientCommsListener implements CommsListener { | ||||
| @@ -33,26 +30,12 @@ public class DefaultClientCommsListener implements CommsListener { | ||||
| 	} | ||||
|  | ||||
| 	private void setLocalPlayer(PacketSetLocalPlayer packet) { | ||||
| 		EntityData entity = getClient().getWorld().getData().getEntity( | ||||
| 				packet.getLocalPlayerEntityId() | ||||
| 		); | ||||
| 		 | ||||
| 		if (entity == null) { | ||||
| 			CrashReports.report( | ||||
| 					null, | ||||
| 					"Player entity with ID %s not found", | ||||
| 					new String(StringUtil.toFullHex(packet.getLocalPlayerEntityId())) | ||||
| 			); | ||||
| 		} | ||||
|  | ||||
| 		getClient().setLocalPlayer(entity); | ||||
| 		getClient().getCamera().setAnchor(new EntityAnchor( | ||||
| 				getClient().getWorld().getEntityRenderable(entity) | ||||
| 		)); | ||||
| 		getClient().getLocalPlayer().setEntityId(packet.getEntityId()); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void onIOError(IOException reason) { | ||||
| 		CrashReports.report(reason, "An IOException has occurred in communications"); | ||||
| 		// TODO implement | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -192,6 +192,7 @@ public class VertexBufferObject implements OpenGLDeletable { | ||||
| 		return usage; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public int getHandle() { | ||||
| 		return handle; | ||||
| 	} | ||||
|   | ||||
| @@ -91,6 +91,7 @@ public class Shader implements OpenGLDeletable { | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public int getHandle() { | ||||
| 		return handle; | ||||
| 	} | ||||
|   | ||||
| @@ -24,6 +24,7 @@ import ru.windcorp.progressia.client.graphics.input.bus.Input; | ||||
| public abstract class GUILayer extends AssembledFlatLayer { | ||||
| 	 | ||||
| 	private final Component root = new Component("Root") { | ||||
| 		@Override | ||||
| 		protected void handleReassemblyRequest() { | ||||
| 			GUILayer.this.invalidate(); | ||||
| 		} | ||||
|   | ||||
| @@ -264,6 +264,7 @@ public class ShapeRenderProgram extends Program { | ||||
| 		 | ||||
| 		private final List<Vertex> vertices = new ArrayList<>(); | ||||
| 		 | ||||
| 		@Override | ||||
| 		public VertexBuilder addVertex( | ||||
| 				float x, float y, float z, | ||||
| 				float r, float g, float b, | ||||
| @@ -278,6 +279,7 @@ public class ShapeRenderProgram extends Program { | ||||
| 			return this; | ||||
| 		} | ||||
| 		 | ||||
| 		@Override | ||||
| 		public VertexBuilder addVertex( | ||||
| 				Vec3 position, | ||||
| 				Vec3 colorMultiplier, | ||||
| @@ -292,6 +294,7 @@ public class ShapeRenderProgram extends Program { | ||||
| 			return this; | ||||
| 		} | ||||
| 		 | ||||
| 		@Override | ||||
| 		public ByteBuffer assemble() { | ||||
| 			ByteBuffer result = BufferUtils.createByteBuffer( | ||||
| 					DEFAULT_BYTES_PER_VERTEX * vertices.size() | ||||
|   | ||||
| @@ -75,7 +75,9 @@ public class LayerWorld extends Layer { | ||||
| 			renderWorld(); | ||||
| 		} | ||||
| 		 | ||||
| 		if (client.getLocalPlayer() != null) { | ||||
| 		client.getLocalPlayer().getEntity(); | ||||
| 		 | ||||
| 		if (client.isReady()) { | ||||
| 			client.getLocalPlayer().update(client.getWorld()); | ||||
| 		} | ||||
| 	} | ||||
| @@ -139,10 +141,9 @@ public class LayerWorld extends Layer { | ||||
| 	private static final Renderable SELECTION_BOX = tmp_createSelectionBox(); | ||||
|  | ||||
| 	private void tmp_drawSelectionBox() { | ||||
| 		LocalPlayer player = client.getLocalPlayer(); | ||||
| 		if (player == null) return; | ||||
| 		if (!client.isReady()) return; | ||||
| 		 | ||||
| 		Vec3i selection = player.getSelection().getBlock(); | ||||
| 		Vec3i selection = client.getLocalPlayer().getSelection().getBlock(); | ||||
| 		if (selection == null) return; | ||||
| 		 | ||||
| 		helper.pushTransform().translate(selection.x, selection.y, selection.z); | ||||
|   | ||||
| @@ -1,16 +1,59 @@ | ||||
| package ru.windcorp.progressia.client.graphics.world; | ||||
|  | ||||
| import ru.windcorp.progressia.client.Client; | ||||
| import ru.windcorp.progressia.client.world.WorldRender; | ||||
| import ru.windcorp.progressia.client.world.entity.EntityRenderable; | ||||
| import ru.windcorp.progressia.common.world.PlayerData; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
|  | ||||
| public class LocalPlayer extends PlayerData { | ||||
| public class LocalPlayer { | ||||
|  | ||||
| 	private final Client client; | ||||
| 	 | ||||
| 	private long entityId = EntityData.NULL_ENTITY_ID; | ||||
| 	private EntityData lastKnownEntity = null; | ||||
| 	 | ||||
| 	private final Selection selection = new Selection(); | ||||
| 	 | ||||
| 	public LocalPlayer(EntityData entity) { | ||||
| 		super(entity); | ||||
| 	public LocalPlayer(Client client) { | ||||
| 		this.client = client; | ||||
| 	} | ||||
| 	 | ||||
| 	public Client getClient() { | ||||
| 		return client; | ||||
| 	} | ||||
| 	 | ||||
| 	public long getEntityId() { | ||||
| 		return entityId; | ||||
| 	} | ||||
| 	 | ||||
| 	public void setEntityId(long entityId) { | ||||
| 		this.entityId = entityId; | ||||
| 	 | ||||
| 		this.lastKnownEntity = null; | ||||
| 		getEntity(); | ||||
| 	} | ||||
| 	 | ||||
| 	public boolean hasEntityId() { | ||||
| 		return entityId != EntityData.NULL_ENTITY_ID; | ||||
| 	} | ||||
| 	 | ||||
| 	public boolean hasEntity() { | ||||
| 		return getEntity() != null; | ||||
| 	} | ||||
| 	 | ||||
| 	public EntityData getEntity() { | ||||
| 		if (!hasEntityId()) { | ||||
| 			return null; | ||||
| 		} | ||||
| 		 | ||||
| 		EntityData entity = getClient().getWorld().getData().getEntity(getEntityId()); | ||||
| 		 | ||||
| 		if (entity != lastKnownEntity) { | ||||
| 			getClient().onLocalPlayerEntityChanged(entity, lastKnownEntity); | ||||
| 			this.lastKnownEntity = entity; | ||||
| 		} | ||||
| 		 | ||||
| 		return entity; | ||||
| 	} | ||||
| 	 | ||||
| 	public Selection getSelection() { | ||||
|   | ||||
| @@ -55,6 +55,7 @@ public abstract class MutableString { | ||||
| 		return data; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public String toString() { | ||||
| 		return get(); | ||||
| 	} | ||||
|   | ||||
| @@ -18,8 +18,11 @@ | ||||
| package ru.windcorp.progressia.client.world; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
| import java.util.WeakHashMap; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| import glm.mat._4.Mat4; | ||||
| @@ -38,23 +41,63 @@ import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSupplier; | ||||
| import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizers; | ||||
| import ru.windcorp.progressia.client.world.tile.TileRender; | ||||
| import ru.windcorp.progressia.client.world.tile.TileRenderRegistry; | ||||
| import ru.windcorp.progressia.client.world.tile.TileRenderStack; | ||||
| import ru.windcorp.progressia.common.world.ChunkData; | ||||
| import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericChunk; | ||||
| import ru.windcorp.progressia.common.world.tile.TileData; | ||||
| import ru.windcorp.progressia.common.world.tile.TileDataStack; | ||||
|  | ||||
| public class ChunkRender { | ||||
| public class ChunkRender | ||||
| implements GenericChunk< | ||||
| 	ChunkRender, | ||||
| 	BlockRender, | ||||
| 	TileRender, | ||||
| 	TileRenderStack | ||||
| > { | ||||
|  | ||||
| 	private final WorldRender world; | ||||
| 	private final ChunkData data; | ||||
|  | ||||
| 	private boolean needsUpdate; | ||||
| 	private Model model = null; | ||||
| 	 | ||||
| 	private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists = | ||||
| 			Collections.synchronizedMap(new WeakHashMap<>()); | ||||
| 	 | ||||
| 	public ChunkRender(WorldRender world, ChunkData data) { | ||||
| 		this.world = world; | ||||
| 		this.data = data; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public Vec3i getPosition() { | ||||
| 		return getData().getPosition(); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public BlockRender getBlock(Vec3i posInChunk) { | ||||
| 		return BlockRenderRegistry.getInstance().get( | ||||
| 				getData().getBlock(posInChunk).getId() | ||||
| 		); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public TileRenderStack getTiles(Vec3i blockInChunk, BlockFace face) { | ||||
| 		return getTileStackWrapper(getData().getTiles(blockInChunk, face)); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public boolean hasTiles(Vec3i blockInChunk, BlockFace face) { | ||||
| 		return getData().hasTiles(blockInChunk, face); | ||||
| 	} | ||||
| 	 | ||||
| 	private TileRenderStack getTileStackWrapper(TileDataStack tileDataList) { | ||||
| 		return tileRenderLists.computeIfAbsent( | ||||
| 				tileDataList, | ||||
| 				TileRenderStackImpl::new | ||||
| 		); | ||||
| 	} | ||||
| 	 | ||||
| 	public WorldRender getWorld() { | ||||
| 		return world; | ||||
| 	} | ||||
| @@ -63,23 +106,13 @@ public class ChunkRender { | ||||
| 		return data; | ||||
| 	} | ||||
| 	 | ||||
| 	public void markForUpdate() { | ||||
| 		this.needsUpdate = true; | ||||
| 	public synchronized void markForUpdate() { | ||||
| 		getWorld().markChunkForUpdate(getPosition()); | ||||
| 	} | ||||
| 	 | ||||
| 	public boolean needsUpdate() { | ||||
| 		return needsUpdate; | ||||
| 	} | ||||
| 	 | ||||
| 	public BlockRender getBlock(Vec3i posInChunk) { | ||||
| 		return BlockRenderRegistry.getInstance().get( | ||||
| 				getData().getBlock(posInChunk).getId() | ||||
| 		); | ||||
| 	} | ||||
| 	 | ||||
| 	public void render(ShapeRenderHelper renderer) { | ||||
| 		if (model == null || needsUpdate()) { | ||||
| 			buildModel(); | ||||
| 	public synchronized void render(ShapeRenderHelper renderer) { | ||||
| 		if (model == null) { | ||||
| 			return; | ||||
| 		} | ||||
| 		 | ||||
| 		renderer.pushTransform().translate( | ||||
| @@ -93,7 +126,7 @@ public class ChunkRender { | ||||
| 		renderer.popTransform(); | ||||
| 	} | ||||
|  | ||||
| 	private void buildModel() { | ||||
| 	public synchronized void update() { | ||||
| 		Collection<ChunkRenderOptimizer> optimizers = | ||||
| 				ChunkRenderOptimizers.getAllSuppliers().stream() | ||||
| 				.map(ChunkRenderOptimizerSupplier::createOptimizer) | ||||
| @@ -121,7 +154,6 @@ public class ChunkRender { | ||||
| 				.forEach(builder::addPart); | ||||
| 		 | ||||
| 		model = new StaticModel(builder); | ||||
| 		needsUpdate = false; | ||||
| 	} | ||||
|  | ||||
| 	private void buildBlock( | ||||
| @@ -233,4 +265,44 @@ public class ChunkRender { | ||||
| 		); | ||||
| 	} | ||||
| 	 | ||||
| 	private class TileRenderStackImpl extends TileRenderStack { | ||||
| 		 | ||||
| 		private final TileDataStack parent; | ||||
|  | ||||
| 		public TileRenderStackImpl(TileDataStack parent) { | ||||
| 			this.parent = parent; | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public Vec3i getBlockInChunk(Vec3i output) { | ||||
| 			return parent.getBlockInChunk(output); | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public ChunkRender getChunk() { | ||||
| 			return ChunkRender.this; | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public BlockFace getFace() { | ||||
| 			return parent.getFace(); | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public TileRender get(int index) { | ||||
| 			return TileRenderRegistry.getInstance().get(parent.get(index).getId()); | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public int size() { | ||||
| 			return parent.size(); | ||||
| 		} | ||||
| 		 | ||||
| 		@Override | ||||
| 		public TileDataStack getData() { | ||||
| 			return parent; | ||||
| 		} | ||||
| 		 | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -20,79 +20,172 @@ package ru.windcorp.progressia.client.world; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.Iterator; | ||||
| import java.util.Map; | ||||
| import java.util.WeakHashMap; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.client.Client; | ||||
| import ru.windcorp.progressia.client.graphics.backend.FaceCulling; | ||||
| import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; | ||||
| import ru.windcorp.progressia.client.world.block.BlockRender; | ||||
| import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry; | ||||
| import ru.windcorp.progressia.client.world.entity.EntityRenderable; | ||||
| import ru.windcorp.progressia.client.world.tile.TileRender; | ||||
| import ru.windcorp.progressia.client.world.tile.TileRenderStack; | ||||
| import ru.windcorp.progressia.common.util.VectorUtil; | ||||
| import ru.windcorp.progressia.common.world.ChunkData; | ||||
| import ru.windcorp.progressia.common.world.ChunkDataListeners; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
| import ru.windcorp.progressia.common.world.WorldDataListener; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
| import ru.windcorp.progressia.common.world.generic.ChunkSet; | ||||
| import ru.windcorp.progressia.common.world.generic.ChunkSets; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericWorld; | ||||
|  | ||||
| public class WorldRender { | ||||
| public class WorldRender | ||||
| implements GenericWorld< | ||||
| 	BlockRender, | ||||
| 	TileRender, | ||||
| 	TileRenderStack, | ||||
| 	ChunkRender, | ||||
| 	EntityRenderable | ||||
| > { | ||||
|  | ||||
| 	private final WorldData data; | ||||
| 	private final Client client; | ||||
| 	 | ||||
| 	private final Map<ChunkData, ChunkRender> chunks = new HashMap<>(); | ||||
| 	private final Map<ChunkData, ChunkRender> chunks = | ||||
| 			Collections.synchronizedMap(new HashMap<>()); | ||||
| 	private final Map<EntityData, EntityRenderable> entityModels = | ||||
| 			Collections.synchronizedMap(new WeakHashMap<>()); | ||||
| 	 | ||||
| 	public WorldRender(WorldData data) { | ||||
| 	private final ChunkSet chunksToUpdate = ChunkSets.newSyncHashSet(); | ||||
| 	 | ||||
| 	public WorldRender(WorldData data, Client client) { | ||||
| 		this.data = data; | ||||
| 		this.client = client; | ||||
| 		 | ||||
| 		data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this))); | ||||
| 		data.addListener(new WorldDataListener() { | ||||
| 			@Override | ||||
| 			public void onChunkLoaded(WorldData world, ChunkData chunk) { | ||||
| 				chunks.put(chunk, new ChunkRender(WorldRender.this, chunk)); | ||||
| 				addChunk(chunk); | ||||
| 			} | ||||
| 			 | ||||
| 			@Override | ||||
| 			public void beforeChunkUnloaded(WorldData world, ChunkData chunk) { | ||||
| 				chunks.remove(chunk); | ||||
| 				removeChunk(chunk); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	protected void addChunk(ChunkData chunk) { | ||||
| 		chunks.put(chunk, new ChunkRender(WorldRender.this, chunk)); | ||||
| 		markChunkForUpdate(chunk.getPosition()); | ||||
| 	} | ||||
| 	 | ||||
| 	protected void removeChunk(ChunkData chunk) { | ||||
| 		chunks.remove(chunk); | ||||
| 	} | ||||
|  | ||||
| 	public WorldData getData() { | ||||
| 		return data; | ||||
| 	} | ||||
| 	 | ||||
| 	public Client getClient() { | ||||
| 		return client; | ||||
| 	} | ||||
| 	 | ||||
| 	public ChunkRender getChunk(ChunkData chunkData) { | ||||
| 		return chunks.get(chunkData); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public ChunkRender getChunk(Vec3i pos) { | ||||
| 		return chunks.get(getData().getChunk(pos)); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public Collection<ChunkRender> getChunks() { | ||||
| 		return chunks.values(); | ||||
| 	} | ||||
| 	 | ||||
| 	public void render(ShapeRenderHelper renderer) { | ||||
| 		for (ChunkRender chunk : getChunks()) { | ||||
| 			chunk.render(renderer); | ||||
| 		} | ||||
| 	@Override | ||||
| 	public Collection<EntityRenderable> getEntities() { | ||||
| 		return entityModels.values(); | ||||
| 	} | ||||
| 	 | ||||
| 	public void render(ShapeRenderHelper renderer) { | ||||
| 		updateChunks(); | ||||
| 		 | ||||
| 		getChunks().forEach(chunk -> chunk.render(renderer)); | ||||
| 		renderEntities(renderer); | ||||
| 	} | ||||
| 	 | ||||
| 	private void updateChunks() { | ||||
| 		synchronized (chunksToUpdate) { | ||||
| 			if (chunksToUpdate.isEmpty()) return; | ||||
| 			 | ||||
| 			int updates = updateChunksNearLocalPlayer(); | ||||
| 			int maximumUpdates = getMaximumChunkUpdatesPerFrame(); | ||||
| 			 | ||||
| 			updateRandomChunks(maximumUpdates - updates); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private int updateChunksNearLocalPlayer() { | ||||
| 		EntityData entity = getClient().getLocalPlayer().getEntity(); | ||||
| 		if (entity == null) return 0; | ||||
| 		 | ||||
| 		int[] updates = new int[] { 0 }; | ||||
| 		 | ||||
| 		VectorUtil.iterateCuboidAround(entity.getChunkCoords(null), 3, chunkPos -> { | ||||
| 			if (chunksToUpdate.contains(chunkPos)) { | ||||
| 				getChunk(chunkPos).update(); | ||||
| 				chunksToUpdate.remove(chunkPos); | ||||
| 				 | ||||
| 				updates[0]++; | ||||
| 			} | ||||
| 		}); | ||||
| 		 | ||||
| 		return updates[0]; | ||||
| 	} | ||||
| 	 | ||||
| 	private void updateRandomChunks(int allowedUpdates) { | ||||
| 		if (allowedUpdates <= 0) return; | ||||
| 		 | ||||
| 		for (Iterator<Vec3i> it = chunksToUpdate.iterator(); it.hasNext();) { | ||||
| 			Vec3i chunkPos = it.next(); | ||||
| 			ChunkRender chunk = getChunk(chunkPos); | ||||
| 			 | ||||
| 			if (chunk != null) { | ||||
| 				chunk.update(); | ||||
| 				allowedUpdates--; | ||||
| 			} | ||||
| 			 | ||||
| 			it.remove(); | ||||
| 			 | ||||
| 			if (allowedUpdates <= 0) return; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private int getMaximumChunkUpdatesPerFrame() { | ||||
| 		return 1; | ||||
| 	} | ||||
| 	 | ||||
| 	public int getPendingChunkUpdates() { | ||||
| 		return chunksToUpdate.size(); | ||||
| 	} | ||||
|  | ||||
| 	private void renderEntities(ShapeRenderHelper renderer) { | ||||
| 		FaceCulling.push(false); | ||||
| 		 | ||||
| 		for (ChunkRender chunk : getChunks()) { | ||||
| 			chunk.getData().forEachEntity(entity -> { | ||||
| 					renderer.pushTransform().translate(entity.getPosition()); | ||||
| 					getEntityRenderable(entity).render(renderer); | ||||
| 					renderer.popTransform(); | ||||
| 			}); | ||||
| 		} | ||||
| 		getData().forEachEntity(entity -> { | ||||
| 			renderer.pushTransform().translate(entity.getPosition()); | ||||
| 			getEntityRenderable(entity).render(renderer); | ||||
| 			renderer.popTransform(); | ||||
| 		}); | ||||
| 		 | ||||
| 		FaceCulling.pop(); | ||||
| 	} | ||||
| @@ -109,4 +202,10 @@ public class WorldRender { | ||||
| 				.createRenderable(entity); | ||||
| 	} | ||||
| 	 | ||||
| 	public void markChunkForUpdate(Vec3i chunkPos) { | ||||
| 		if (getData().getChunk(chunkPos) != null) { | ||||
| 			chunksToUpdate.add(chunkPos); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -19,9 +19,10 @@ package ru.windcorp.progressia.client.world.block; | ||||
|  | ||||
| import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; | ||||
| import ru.windcorp.progressia.common.util.namespaces.Namespaced; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericBlock; | ||||
| import ru.windcorp.progressia.client.graphics.model.Renderable; | ||||
|  | ||||
| public abstract class BlockRender extends Namespaced { | ||||
| public abstract class BlockRender extends Namespaced implements GenericBlock { | ||||
| 	 | ||||
| 	public BlockRender(String id) { | ||||
| 		super(id); | ||||
|   | ||||
| @@ -18,8 +18,8 @@ | ||||
| package ru.windcorp.progressia.client.world.cro; | ||||
|  | ||||
| import static ru.windcorp.progressia.common.world.ChunkData.BLOCKS_PER_CHUNK; | ||||
| import static ru.windcorp.progressia.common.world.tile.GenericTileStack.TILES_PER_FACE; | ||||
| import static ru.windcorp.progressia.common.world.block.BlockFace.BLOCK_FACE_COUNT; | ||||
| import static ru.windcorp.progressia.common.world.generic.GenericTileStack.TILES_PER_FACE; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
|   | ||||
| @@ -3,8 +3,9 @@ package ru.windcorp.progressia.client.world.entity; | ||||
| import glm.vec._3.Vec3; | ||||
| import ru.windcorp.progressia.client.graphics.model.Renderable; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericEntity; | ||||
|  | ||||
| public abstract class EntityRenderable implements Renderable { | ||||
| public abstract class EntityRenderable implements Renderable, GenericEntity { | ||||
| 	 | ||||
| 	private final EntityData data; | ||||
| 	 | ||||
| @@ -16,6 +17,16 @@ public abstract class EntityRenderable implements Renderable { | ||||
| 		return data; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public Vec3 getPosition() { | ||||
| 		return getData().getPosition(); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public String getId() { | ||||
| 		return getData().getId(); | ||||
| 	} | ||||
| 	 | ||||
| 	public void getViewPoint(Vec3 output) { | ||||
| 		output.set(0, 0, 0); | ||||
| 	} | ||||
|   | ||||
| @@ -116,6 +116,8 @@ public abstract class NPedModel extends EntityRenderable { | ||||
| 		this.body = body; | ||||
| 		this.head = head; | ||||
| 		this.scale = scale; | ||||
| 		 | ||||
| 		evaluateAngles(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
|   | ||||
| @@ -5,8 +5,9 @@ import ru.windcorp.progressia.client.graphics.model.Renderable; | ||||
| import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer; | ||||
| import ru.windcorp.progressia.common.util.namespaces.Namespaced; | ||||
| import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericTile; | ||||
|  | ||||
| public class TileRender extends Namespaced { | ||||
| public class TileRender extends Namespaced implements GenericTile { | ||||
|  | ||||
| 	public TileRender(String id) { | ||||
| 		super(id); | ||||
|   | ||||
| @@ -0,0 +1,16 @@ | ||||
| package ru.windcorp.progressia.client.world.tile; | ||||
|  | ||||
| import ru.windcorp.progressia.client.world.ChunkRender; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericTileStack; | ||||
| import ru.windcorp.progressia.common.world.tile.TileDataStack; | ||||
|  | ||||
| public abstract class TileRenderStack | ||||
| extends GenericTileStack< | ||||
| 	TileRenderStack, | ||||
| 	TileRender, | ||||
| 	ChunkRender | ||||
| > { | ||||
| 	 | ||||
| 	public abstract TileDataStack getData(); | ||||
| 	 | ||||
| } | ||||
| @@ -1,6 +1,11 @@ | ||||
| package ru.windcorp.progressia.common.comms.controls; | ||||
|  | ||||
| import java.io.DataInput; | ||||
| import java.io.DataOutput; | ||||
| import java.io.IOException; | ||||
|  | ||||
| import ru.windcorp.progressia.common.comms.packets.Packet; | ||||
| import ru.windcorp.progressia.common.world.DecodingException; | ||||
|  | ||||
| public class PacketControl extends Packet { | ||||
| 	 | ||||
| @@ -15,4 +20,14 @@ public class PacketControl extends Packet { | ||||
| 		return control; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void read(DataInput input) throws IOException, DecodingException { | ||||
| 		// TODO implement controls | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void write(DataOutput output) throws IOException { | ||||
| 		// implement controls | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,19 @@ | ||||
| package ru.windcorp.progressia.common.comms.packets; | ||||
|  | ||||
| import ru.windcorp.progressia.common.util.namespaces.Namespaced; | ||||
| import java.io.DataInput; | ||||
| import java.io.DataOutput; | ||||
| import java.io.IOException; | ||||
|  | ||||
| public class Packet extends Namespaced { | ||||
| import ru.windcorp.progressia.common.util.namespaces.Namespaced; | ||||
| import ru.windcorp.progressia.common.world.DecodingException; | ||||
|  | ||||
| public abstract class Packet extends Namespaced { | ||||
|  | ||||
| 	public Packet(String id) { | ||||
| 		super(id); | ||||
| 	} | ||||
|  | ||||
| 	public abstract void read(DataInput input) throws IOException, DecodingException; | ||||
| 	public abstract void write(DataOutput output) throws IOException; | ||||
| 	 | ||||
| } | ||||
|   | ||||
| @@ -1,38 +0,0 @@ | ||||
| package ru.windcorp.progressia.common.comms.packets; | ||||
|  | ||||
| import java.io.IOException; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.io.ChunkIO; | ||||
| import ru.windcorp.progressia.common.util.DataBuffer; | ||||
| import ru.windcorp.progressia.common.util.crash.CrashReports; | ||||
| import ru.windcorp.progressia.common.world.DecodingException; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
|  | ||||
| public class PacketLoadChunk extends PacketWorldChange { | ||||
| 	 | ||||
| 	private final DataBuffer data = new DataBuffer(); | ||||
| 	private final Vec3i position = new Vec3i(); | ||||
|  | ||||
| 	public PacketLoadChunk(String id) { | ||||
| 		super(id); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void apply(WorldData world) { | ||||
| 		try { | ||||
| 			world.addChunk(ChunkIO.load(world, position, data.getInputStream())); | ||||
| 		} catch (DecodingException | IOException e) { | ||||
| 			CrashReports.report(e, "Could not load chunk"); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	public Vec3i getPosition() { | ||||
| 		return position; | ||||
| 	} | ||||
| 	 | ||||
| 	public DataBuffer getData() { | ||||
| 		return data; | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -1,24 +0,0 @@ | ||||
| package ru.windcorp.progressia.common.comms.packets; | ||||
|  | ||||
| public class PacketSetLocalPlayer extends Packet { | ||||
| 	 | ||||
| 	private long localPlayerEntityId; | ||||
|  | ||||
| 	public PacketSetLocalPlayer(long entityId) { | ||||
| 		this("Core:SetLocalPlayer", entityId); | ||||
| 	} | ||||
| 	 | ||||
| 	protected PacketSetLocalPlayer(String id, long entityId) { | ||||
| 		super(id); | ||||
| 		this.localPlayerEntityId = entityId; | ||||
| 	} | ||||
| 	 | ||||
| 	public long getLocalPlayerEntityId() { | ||||
| 		return localPlayerEntityId; | ||||
| 	} | ||||
| 	 | ||||
| 	public void setLocalPlayerEntityId(long localPlayerEntityId) { | ||||
| 		this.localPlayerEntityId = localPlayerEntityId; | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -8,10 +8,12 @@ public class OptimizedStateStorage extends StateStorage { | ||||
| 		this.ints = new int[sizes.getInts()]; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public int getInt(int index) { | ||||
| 		return ints[index]; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void setInt(int index, int value) { | ||||
| 		ints[index] = value; | ||||
| 	} | ||||
|   | ||||
| @@ -32,6 +32,7 @@ public class ByteBufferInputStream extends InputStream { | ||||
| 		this.buffer = buffer; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public int read() { | ||||
| 		if (!buffer.hasRemaining()) { | ||||
| 			return -1; | ||||
| @@ -39,6 +40,7 @@ public class ByteBufferInputStream extends InputStream { | ||||
| 		return buffer.get() & 0xFF; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public int read(byte[] bytes, int off, int len) { | ||||
| 		if (!buffer.hasRemaining()) { | ||||
| 			return -1; | ||||
|   | ||||
| @@ -30,6 +30,7 @@ public class ByteBufferOutputStream extends OutputStream { | ||||
| 		this.buffer = buffer; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void write(int b) throws IOException { | ||||
| 		try { | ||||
| 			buffer.put((byte) b); | ||||
| @@ -38,6 +39,7 @@ public class ByteBufferOutputStream extends OutputStream { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void write(byte[] bytes, int off, int len) throws IOException { | ||||
| 		try { | ||||
| 			buffer.put(bytes, off, len); | ||||
|   | ||||
| @@ -25,7 +25,7 @@ public class DataBuffer { | ||||
| 		@Override | ||||
| 		public int read() throws IOException { | ||||
| 			if (DataBuffer.this.position >= buffer.size()) return -1; | ||||
| 			int result = buffer.getQuick(DataBuffer.this.position); | ||||
| 			int result = buffer.getQuick(DataBuffer.this.position) & 0xFF; | ||||
| 			++DataBuffer.this.position; | ||||
| 			return result; | ||||
| 		} | ||||
|   | ||||
| @@ -44,6 +44,7 @@ public class SizeLimitedList<E> extends ForwardingList<E> { | ||||
| 		return standardAddAll(index, elements); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public boolean add(E e) { | ||||
| 		checkMaxSize(); | ||||
| 		return delegate().add(e); | ||||
|   | ||||
| @@ -19,10 +19,10 @@ public class VectorUtil { | ||||
| 		X, Y, Z, W; | ||||
| 	} | ||||
| 	 | ||||
| 	public static void forEachVectorInCuboid( | ||||
| 	public static void iterateCuboid( | ||||
| 			int x0, int y0, int z0, | ||||
| 			int x1, int y1, int z1, | ||||
| 			Consumer<Vec3i> action | ||||
| 			Consumer<? super Vec3i> action | ||||
| 	) { | ||||
| 		Vec3i cursor = Vectors.grab3i(); | ||||
| 		 | ||||
| @@ -38,6 +38,57 @@ public class VectorUtil { | ||||
| 		Vectors.release(cursor); | ||||
| 	} | ||||
| 	 | ||||
| 	public static void iterateCuboid( | ||||
| 			Vec3i vMin, Vec3i vMax, | ||||
| 			Consumer<? super Vec3i> action | ||||
| 	) { | ||||
| 		iterateCuboid(vMin.x, vMin.y, vMin.z, vMax.x, vMax.y, vMax.z, action); | ||||
| 	} | ||||
| 	 | ||||
| 	public static void iterateCuboidAround( | ||||
| 			int cx, int cy, int cz, | ||||
| 			int dx, int dy, int dz, | ||||
| 			Consumer<? super Vec3i> action | ||||
| 	) { | ||||
| 		if (dx < 0) throw new IllegalArgumentException("dx " + dx + " is negative"); | ||||
| 		if (dy < 0) throw new IllegalArgumentException("dy " + dx + " is negative"); | ||||
| 		if (dz < 0) throw new IllegalArgumentException("dz " + dx + " is negative"); | ||||
| 		 | ||||
| 		if (dx % 2 == 0) throw new IllegalArgumentException("dx " + dx + " is even, only odd accepted"); | ||||
| 		if (dy % 2 == 0) throw new IllegalArgumentException("dy " + dy + " is even, only odd accepted"); | ||||
| 		if (dz % 2 == 0) throw new IllegalArgumentException("dz " + dz + " is even, only odd accepted"); | ||||
| 		 | ||||
| 		dx /= 2; | ||||
| 		dy /= 2; | ||||
| 		dz /= 2; | ||||
| 		 | ||||
| 		iterateCuboid(cx - dx, cy - dy, cz - dz, cx + dx + 1, cy + dy + 1, cz + dz + 1, action); | ||||
| 	} | ||||
| 	 | ||||
| 	public static void iterateCuboidAround( | ||||
| 			Vec3i center, | ||||
| 			Vec3i diameters, | ||||
| 			Consumer<? super Vec3i> action | ||||
| 	) { | ||||
| 		iterateCuboidAround(center.x, center.y, center.z, diameters.x, diameters.y, diameters.z, action); | ||||
| 	} | ||||
| 	 | ||||
| 	public static void iterateCuboidAround( | ||||
| 			int cx, int cy, int cz, | ||||
| 			int diameter, | ||||
| 			Consumer<? super Vec3i> action | ||||
| 	) { | ||||
| 		iterateCuboidAround(cx, cy, cz, diameter, diameter, diameter, action); | ||||
| 	} | ||||
| 	 | ||||
| 	public static void iterateCuboidAround( | ||||
| 			Vec3i center, | ||||
| 			int diameter, | ||||
| 			Consumer<? super Vec3i> action | ||||
| 	) { | ||||
| 		iterateCuboidAround(center.x, center.y, center.z, diameter, action); | ||||
| 	} | ||||
| 	 | ||||
| 	public static void applyMat4(Vec3 in, Mat4 mat, Vec3 out) { | ||||
| 		Vec4 vec4 = Vectors.grab4(); | ||||
| 		vec4.set(in, 1f); | ||||
|   | ||||
| @@ -23,7 +23,6 @@ import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
| import java.util.function.BiConsumer; | ||||
| import java.util.function.Consumer; | ||||
| @@ -32,13 +31,19 @@ import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.util.VectorUtil; | ||||
| import ru.windcorp.progressia.common.world.block.BlockData; | ||||
| import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericChunk; | ||||
| import ru.windcorp.progressia.common.world.tile.TileData; | ||||
| import ru.windcorp.progressia.common.world.tile.TileDataStack; | ||||
| import ru.windcorp.progressia.common.world.tile.TileReference; | ||||
| import ru.windcorp.progressia.common.world.tile.TileStackIsFullException; | ||||
|  | ||||
| public class ChunkData { | ||||
| public class ChunkData | ||||
| implements GenericChunk< | ||||
| 	ChunkData, | ||||
| 	BlockData, | ||||
| 	TileData, | ||||
| 	TileDataStack | ||||
| > { | ||||
| 	 | ||||
| 	public static final int BLOCKS_PER_CHUNK = Coordinates.CHUNK_SIZE; | ||||
| 	 | ||||
| @@ -54,9 +59,6 @@ public class ChunkData { | ||||
| 		BLOCK_FACE_COUNT | ||||
| 	]; | ||||
| 	 | ||||
| 	private final List<EntityData> entities = | ||||
| 			Collections.synchronizedList(new ArrayList<>()); | ||||
| 	 | ||||
| 	private final Collection<ChunkDataListener> listeners = | ||||
| 			Collections.synchronizedCollection(new ArrayList<>()); | ||||
| 	 | ||||
| @@ -65,6 +67,12 @@ public class ChunkData { | ||||
| 		this.world = world; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public Vec3i getPosition() { | ||||
| 		return position; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public BlockData getBlock(Vec3i posInChunk) { | ||||
| 		return blocks[getBlockIndex(posInChunk)]; | ||||
| 	} | ||||
| @@ -81,6 +89,7 @@ public class ChunkData { | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public TileDataStack getTilesOrNull(Vec3i blockInChunk, BlockFace face) { | ||||
| 		return tiles[getTileIndex(blockInChunk, face)]; | ||||
| 	} | ||||
| @@ -98,10 +107,12 @@ public class ChunkData { | ||||
| 		this.tiles[getTileIndex(blockInChunk, face)] = tiles; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public boolean hasTiles(Vec3i blockInChunk, BlockFace face) { | ||||
| 		return getTilesOrNull(blockInChunk, face) != null; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public TileDataStack getTiles(Vec3i blockInChunk, BlockFace face) { | ||||
| 		int index = getTileIndex(blockInChunk, face); | ||||
| 		 | ||||
| @@ -144,10 +155,6 @@ public class ChunkData { | ||||
| 				face.getId(); | ||||
| 	} | ||||
| 	 | ||||
| 	public List<EntityData> getEntities() { | ||||
| 		return entities; | ||||
| 	} | ||||
| 	 | ||||
| 	private static void checkLocalCoordinates(Vec3i posInChunk) { | ||||
| 		if (!isInBounds(posInChunk)) { | ||||
| 			throw new IllegalCoordinatesException( | ||||
| @@ -176,7 +183,7 @@ public class ChunkData { | ||||
| 	} | ||||
| 	 | ||||
| 	public void forEachBlock(Consumer<Vec3i> action) { | ||||
| 		VectorUtil.forEachVectorInCuboid( | ||||
| 		VectorUtil.iterateCuboid( | ||||
| 				0, 0, 0, | ||||
| 				BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, | ||||
| 				action | ||||
| @@ -203,26 +210,6 @@ public class ChunkData { | ||||
| 		forEachTileStack(stack -> stack.forEach(tileData -> action.accept(stack, tileData))); | ||||
| 	} | ||||
| 	 | ||||
| 	public void forEachEntity(Consumer<EntityData> action) { | ||||
| 		getEntities().forEach(action); | ||||
| 	} | ||||
|  | ||||
| 	public int getX() { | ||||
| 		return position.x; | ||||
| 	} | ||||
| 	 | ||||
| 	public int getY() { | ||||
| 		return position.y; | ||||
| 	} | ||||
| 	 | ||||
| 	public int getZ() { | ||||
| 		return position.z; | ||||
| 	} | ||||
| 	 | ||||
| 	public Vec3i getPosition() { | ||||
| 		return position; | ||||
| 	} | ||||
| 	 | ||||
| 	public WorldData getWorld() { | ||||
| 		return world; | ||||
| 	} | ||||
| @@ -407,6 +394,24 @@ public class ChunkData { | ||||
| 			report(null, tile); | ||||
| 		} | ||||
| 		 | ||||
| 		@Override | ||||
| 		public void load(TileData tile, int tag) { | ||||
| 			addFarthest(tile); | ||||
| 			 | ||||
| 			int assignedTag = getIndexByTag(tag); | ||||
| 			 | ||||
| 			if (assignedTag == tag) return; | ||||
| 			if (assignedTag == -1) { | ||||
| 				throw new IllegalArgumentException("Tag " + tag + " already used by tile at index " + getIndexByTag(tag)); | ||||
| 			} | ||||
| 			 | ||||
| 			indicesByTag[tagsByIndex[size() - 1]] = -1; | ||||
| 			tagsByIndex[size() - 1] = tag; | ||||
| 			indicesByTag[tag] = size() - 1; | ||||
| 			 | ||||
| 			assert checkConsistency(); | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public TileData remove(int index) { | ||||
| 			TileData previous = get(index); // checks index | ||||
|   | ||||
| @@ -0,0 +1,13 @@ | ||||
| package ru.windcorp.progressia.common.world; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
|  | ||||
| public abstract class PacketChunkChange extends PacketWorldChange { | ||||
|  | ||||
| 	public PacketChunkChange(String id) { | ||||
| 		super(id); | ||||
| 	} | ||||
|  | ||||
| 	public abstract void getAffectedChunk(Vec3i output); | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,56 @@ | ||||
| package ru.windcorp.progressia.common.world; | ||||
|  | ||||
| import java.io.DataInput; | ||||
| import java.io.DataOutput; | ||||
| import java.io.IOException; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
|  | ||||
| public class PacketRevokeChunk extends PacketChunkChange { | ||||
| 	 | ||||
| 	private final Vec3i position = new Vec3i(); | ||||
| 	 | ||||
| 	public PacketRevokeChunk() { | ||||
| 		this("Core:RevokeChunk"); | ||||
| 	} | ||||
|  | ||||
| 	protected PacketRevokeChunk(String id) { | ||||
| 		super(id); | ||||
| 	} | ||||
| 	 | ||||
| 	public void set(Vec3i chunkPos) { | ||||
| 		this.position.set(chunkPos.x, chunkPos.y, chunkPos.z); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void read(DataInput input) throws IOException { | ||||
| 		this.position.set(input.readInt(), input.readInt(), input.readInt()); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void write(DataOutput output) throws IOException { | ||||
| 		output.writeInt(this.position.x); | ||||
| 		output.writeInt(this.position.y); | ||||
| 		output.writeInt(this.position.z); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void apply(WorldData world) { | ||||
| 		synchronized (world) { | ||||
| 			ChunkData chunk = world.getChunk(position); | ||||
| 			if (chunk != null) { | ||||
| 				world.removeChunk(chunk); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void getAffectedChunk(Vec3i output) { | ||||
| 		output.set(getPosition().x, getPosition().y, getPosition().z); | ||||
| 	} | ||||
| 	 | ||||
| 	public Vec3i getPosition() { | ||||
| 		return position; | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,72 @@ | ||||
| package ru.windcorp.progressia.common.world; | ||||
|  | ||||
| import java.io.DataInput; | ||||
| import java.io.DataOutput; | ||||
| import java.io.IOException; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.io.ChunkIO; | ||||
| import ru.windcorp.progressia.common.util.DataBuffer; | ||||
| import ru.windcorp.progressia.common.util.crash.CrashReports; | ||||
|  | ||||
| public class PacketSendChunk extends PacketChunkChange { | ||||
| 	 | ||||
| 	private final DataBuffer data = new DataBuffer(); | ||||
| 	private final Vec3i position = new Vec3i(); | ||||
| 	 | ||||
| 	public PacketSendChunk() { | ||||
| 		this("Core:SendChunk"); | ||||
| 	} | ||||
|  | ||||
| 	protected PacketSendChunk(String id) { | ||||
| 		super(id); | ||||
| 	} | ||||
| 	 | ||||
| 	public void set(ChunkData chunk) { | ||||
| 		this.position.set(chunk.getX(), chunk.getY(), chunk.getZ()); | ||||
| 		 | ||||
| 		try { | ||||
| 			ChunkIO.save(chunk, this.data.getOutputStream()); | ||||
| 		} catch (IOException e) { | ||||
| 			// Impossible | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void read(DataInput input) throws IOException { | ||||
| 		this.position.set(input.readInt(), input.readInt(), input.readInt()); | ||||
| 		this.data.fill(input, input.readInt()); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void write(DataOutput output) throws IOException { | ||||
| 		output.writeInt(this.position.x); | ||||
| 		output.writeInt(this.position.y); | ||||
| 		output.writeInt(this.position.z); | ||||
| 		output.writeInt(this.data.getSize()); | ||||
| 		this.data.flush(output); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void apply(WorldData world) { | ||||
| 		try { | ||||
| 			world.addChunk(ChunkIO.load(world, position, data.getInputStream())); | ||||
| 		} catch (DecodingException | IOException e) { | ||||
| 			CrashReports.report(e, "Could not load chunk"); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void getAffectedChunk(Vec3i output) { | ||||
| 		output.set(getPosition().x, getPosition().y, getPosition().z); | ||||
| 	} | ||||
| 	 | ||||
| 	public Vec3i getPosition() { | ||||
| 		return position; | ||||
| 	} | ||||
| 	 | ||||
| 	public DataBuffer getData() { | ||||
| 		return data; | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,39 @@ | ||||
| package ru.windcorp.progressia.common.world; | ||||
|  | ||||
| import java.io.DataInput; | ||||
| import java.io.DataOutput; | ||||
| import java.io.IOException; | ||||
|  | ||||
| import ru.windcorp.progressia.common.comms.packets.Packet; | ||||
|  | ||||
| public class PacketSetLocalPlayer extends Packet { | ||||
| 	 | ||||
| 	private long entityId; | ||||
|  | ||||
| 	public PacketSetLocalPlayer() { | ||||
| 		this("Core:SetLocalPlayer"); | ||||
| 	} | ||||
| 	 | ||||
| 	protected PacketSetLocalPlayer(String id) { | ||||
| 		super(id); | ||||
| 	} | ||||
| 	 | ||||
| 	public void set(long entityId) { | ||||
| 		this.entityId = entityId; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void read(DataInput input) throws IOException, DecodingException { | ||||
| 		this.entityId = input.readLong(); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void write(DataOutput output) throws IOException { | ||||
| 		output.writeLong(this.entityId); | ||||
| 	} | ||||
| 	 | ||||
| 	public long getEntityId() { | ||||
| 		return entityId; | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| package ru.windcorp.progressia.common.comms.packets; | ||||
| package ru.windcorp.progressia.common.world; | ||||
| 
 | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
| import ru.windcorp.progressia.common.comms.packets.Packet; | ||||
| 
 | ||||
| public abstract class PacketWorldChange extends Packet { | ||||
| 
 | ||||
| @@ -20,28 +20,42 @@ package ru.windcorp.progressia.common.world; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.Objects; | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import gnu.trove.impl.sync.TSynchronizedLongObjectMap; | ||||
| import gnu.trove.TCollections; | ||||
| import gnu.trove.map.TLongObjectMap; | ||||
| import gnu.trove.map.hash.TLongObjectHashMap; | ||||
| import gnu.trove.set.TLongSet; | ||||
| import ru.windcorp.progressia.common.collision.CollisionModel; | ||||
| import ru.windcorp.progressia.common.util.CoordinatePacker; | ||||
| import ru.windcorp.progressia.common.world.block.BlockData; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
| import ru.windcorp.progressia.test.TestContent; | ||||
| import ru.windcorp.progressia.common.world.generic.ChunkMap; | ||||
| import ru.windcorp.progressia.common.world.generic.ChunkSet; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericWorld; | ||||
| import ru.windcorp.progressia.common.world.generic.LongBasedChunkMap; | ||||
| import ru.windcorp.progressia.common.world.tile.TileData; | ||||
| import ru.windcorp.progressia.common.world.tile.TileDataStack; | ||||
|  | ||||
| public class WorldData { | ||||
| public class WorldData | ||||
| implements GenericWorld< | ||||
| 	BlockData, | ||||
| 	TileData, | ||||
| 	TileDataStack, | ||||
| 	ChunkData, | ||||
| 	EntityData | ||||
| > { | ||||
|  | ||||
| 	private final TLongObjectMap<ChunkData> chunksByPos = | ||||
| 			new TSynchronizedLongObjectMap<>(new TLongObjectHashMap<>(), this); | ||||
| 	private final ChunkMap<ChunkData> chunksByPos = new LongBasedChunkMap<>( | ||||
| 			TCollections.synchronizedMap(new TLongObjectHashMap<>()) | ||||
| 	); | ||||
| 	 | ||||
| 	private final Collection<ChunkData> chunks = | ||||
| 			Collections.unmodifiableCollection(chunksByPos.valueCollection()); | ||||
| 			Collections.unmodifiableCollection(chunksByPos.values()); | ||||
| 	 | ||||
| 	private final TLongObjectMap<EntityData> entitiesById = | ||||
| 			new TSynchronizedLongObjectMap<>(new TLongObjectHashMap<>(), this); | ||||
| 			TCollections.synchronizedMap(new TLongObjectHashMap<>()); | ||||
| 	 | ||||
| 	private final Collection<EntityData> entities = | ||||
| 			Collections.unmodifiableCollection(entitiesById.valueCollection()); | ||||
| @@ -55,21 +69,37 @@ public class WorldData { | ||||
| 		 | ||||
| 	} | ||||
| 	 | ||||
| 	public void tmp_generate() { | ||||
| 		final int size = 10; | ||||
| 		Vec3i cursor = new Vec3i(0, 0, 0); | ||||
| 	@Override | ||||
| 	public ChunkData getChunk(Vec3i pos) { | ||||
| 		return chunksByPos.get(pos); | ||||
| 	} | ||||
| 	 | ||||
| 		for (cursor.x = -(size / 2); cursor.x <= (size / 2); ++cursor.x) { | ||||
| 			for (cursor.y = -(size / 2); cursor.y <= (size / 2); ++cursor.y) { | ||||
| 				for (cursor.z = -(size / 2); cursor.z <= (size / 2); ++cursor.z) { | ||||
| 					ChunkData chunk = new ChunkData(cursor, this); | ||||
| 					TestContent.generateChunk(chunk); | ||||
| 					addChunk(chunk); | ||||
| 				} | ||||
| 			} | ||||
| 	@Override | ||||
| 	public Collection<ChunkData> getChunks() { | ||||
| 		return chunks; | ||||
| 	} | ||||
| 	 | ||||
| 	public ChunkSet getLoadedChunks() { | ||||
| 		return chunksByPos.keys(); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public Collection<EntityData> getEntities() { | ||||
| 		return entities; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void forEachEntity(Consumer<? super EntityData> action) { | ||||
| 		synchronized (entitiesById) { // TODO HORRIBLY MUTILATE THE CORPSE OF TROVE4J so that gnu.trove.impl.sync.SynchronizedCollection.forEach is synchronized | ||||
| 			getEntities().forEach(action); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	 | ||||
| 	public TLongSet getLoadedEntities() { | ||||
| 		return entitiesById.keySet(); | ||||
| 	} | ||||
| 	 | ||||
| 	private void addChunkListeners(ChunkData chunk) { | ||||
| 		getListeners().forEach(l -> l.getChunkListeners(this, chunk.getPosition(), chunk::addListener)); | ||||
| 	} | ||||
| @@ -77,9 +107,7 @@ public class WorldData { | ||||
| 	public synchronized void addChunk(ChunkData chunk) { | ||||
| 		addChunkListeners(chunk); | ||||
| 		 | ||||
| 		long key = getChunkKey(chunk); | ||||
| 		 | ||||
| 		ChunkData previous = chunksByPos.get(key); | ||||
| 		ChunkData previous = chunksByPos.get(chunk); | ||||
| 		if (previous != null) { | ||||
| 			throw new IllegalArgumentException(String.format( | ||||
| 					"Chunk at (%d; %d; %d) already exists", | ||||
| @@ -87,11 +115,7 @@ public class WorldData { | ||||
| 			)); | ||||
| 		} | ||||
| 		 | ||||
| 		chunksByPos.put(key, chunk); | ||||
| 		 | ||||
| 		chunk.forEachEntity(entity -> | ||||
| 			entitiesById.put(entity.getEntityId(), entity) | ||||
| 		); | ||||
| 		chunksByPos.put(chunk, chunk); | ||||
| 		 | ||||
| 		chunk.onLoaded(); | ||||
| 		getListeners().forEach(l -> l.onChunkLoaded(this, chunk)); | ||||
| @@ -101,30 +125,7 @@ public class WorldData { | ||||
| 		getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk)); | ||||
| 		chunk.beforeUnloaded(); | ||||
| 		 | ||||
| 		chunk.forEachEntity(entity -> | ||||
| 			entitiesById.remove(entity.getEntityId()) | ||||
| 		); | ||||
| 		 | ||||
| 		chunksByPos.remove(getChunkKey(chunk)); | ||||
| 	} | ||||
| 	 | ||||
| 	private static long getChunkKey(ChunkData chunk) { | ||||
| 		return CoordinatePacker.pack3IntsIntoLong(chunk.getPosition()); | ||||
| 	} | ||||
| 	 | ||||
| 	public ChunkData getChunk(Vec3i pos) { | ||||
| 		return chunksByPos.get(CoordinatePacker.pack3IntsIntoLong(pos)); | ||||
| 	} | ||||
| 	 | ||||
| 	public ChunkData getChunkByBlock(Vec3i blockInWorld) { | ||||
| 		return getChunk(Coordinates.convertInWorldToChunk(blockInWorld, null)); | ||||
| 	} | ||||
| 	 | ||||
| 	public BlockData getBlock(Vec3i blockInWorld) { | ||||
| 		ChunkData chunk = getChunkByBlock(blockInWorld); | ||||
| 		if (chunk == null) return null; | ||||
| 		 | ||||
| 		return chunk.getBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null)); | ||||
| 		chunksByPos.remove(chunk); | ||||
| 	} | ||||
| 	 | ||||
| 	public void setBlock(Vec3i blockInWorld, BlockData block, boolean notify) { | ||||
| @@ -139,20 +140,47 @@ public class WorldData { | ||||
| 		chunk.setBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null), block, notify); | ||||
| 	} | ||||
| 	 | ||||
| 	public Collection<ChunkData> getChunks() { | ||||
| 		return chunks; | ||||
| 	} | ||||
| 	 | ||||
| 	public TLongSet getChunkKeys() { | ||||
| 		return chunksByPos.keySet(); | ||||
| 	} | ||||
| 	 | ||||
| 	public EntityData getEntity(long entityId) { | ||||
| 		return entitiesById.get(entityId); | ||||
| 	} | ||||
| 	 | ||||
| 	public Collection<EntityData> getEntities() { | ||||
| 		return entities; | ||||
| 	public void addEntity(EntityData entity) { | ||||
| 		Objects.requireNonNull(entity, "entity"); | ||||
| 		 | ||||
| 		EntityData previous = entitiesById.putIfAbsent(entity.getEntityId(), entity); | ||||
| 		 | ||||
| 		if (previous != null) { | ||||
| 			String message = "Cannot add entity " + entity + ": "; | ||||
| 			 | ||||
| 			if (previous == entity) { | ||||
| 				message += "already present"; | ||||
| 			} else { | ||||
| 				message += "entity with the same EntityID already present (" + previous + ")"; | ||||
| 			} | ||||
| 			 | ||||
| 			throw new IllegalStateException(message); | ||||
| 		} | ||||
| 		 | ||||
| 		getListeners().forEach(l -> l.onEntityAdded(this, entity)); | ||||
| 	} | ||||
| 	 | ||||
| 	public void removeEntity(long entityId) { | ||||
| 		synchronized (entitiesById) { | ||||
| 			EntityData entity = entitiesById.get(entityId); | ||||
| 			 | ||||
| 			if (entity == null) { | ||||
| 				throw new IllegalArgumentException("Entity with EntityID " + EntityData.formatEntityId(entityId) + " not present"); | ||||
| 			} else { | ||||
| 				removeEntity(entity); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	public void removeEntity(EntityData entity) { | ||||
| 		Objects.requireNonNull(entity, "entity"); | ||||
| 		 | ||||
| 		getListeners().forEach(l -> l.beforeEntityRemoved(this, entity)); | ||||
| 		entitiesById.remove(entity.getEntityId()); | ||||
| 	} | ||||
| 	 | ||||
| 	public float getTime() { | ||||
|   | ||||
| @@ -3,6 +3,7 @@ package ru.windcorp.progressia.common.world; | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
|  | ||||
| public interface WorldDataListener { | ||||
| 	 | ||||
| @@ -31,4 +32,18 @@ public interface WorldDataListener { | ||||
| 	 */ | ||||
| 	default void beforeChunkUnloaded(WorldData world, ChunkData chunk) {} | ||||
| 	 | ||||
| 	/** | ||||
| 	 * Invoked whenever an {@link EntityData} has been added. | ||||
| 	 * @param world the world instance | ||||
| 	 * @param entity the entity that has been added | ||||
| 	 */ | ||||
| 	default void onEntityAdded(WorldData world, EntityData entity) {} | ||||
| 	 | ||||
| 	/** | ||||
| 	 * Invoked whenever an {@link EntityData} is about to be removed. | ||||
| 	 * @param world the world instance | ||||
| 	 * @param entity the entity that is going to be removed | ||||
| 	 */ | ||||
| 	default void beforeEntityRemoved(WorldData world, EntityData entity) {} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -20,8 +20,9 @@ package ru.windcorp.progressia.common.world.block; | ||||
| import ru.windcorp.progressia.common.collision.AABB; | ||||
| import ru.windcorp.progressia.common.collision.CollisionModel; | ||||
| import ru.windcorp.progressia.common.util.namespaces.Namespaced; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericBlock; | ||||
|  | ||||
| public class BlockData extends Namespaced { | ||||
| public class BlockData extends Namespaced implements GenericBlock { | ||||
|  | ||||
| 	public BlockData(String id) { | ||||
| 		super(id); | ||||
|   | ||||
| @@ -0,0 +1,56 @@ | ||||
| package ru.windcorp.progressia.common.world.block; | ||||
|  | ||||
| import java.io.DataInput; | ||||
| import java.io.DataOutput; | ||||
| import java.io.IOException; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.world.Coordinates; | ||||
| import ru.windcorp.progressia.common.world.DecodingException; | ||||
| import ru.windcorp.progressia.common.world.PacketChunkChange; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
|  | ||||
| public class PacketSetBlock extends PacketChunkChange { | ||||
| 	 | ||||
| 	private String id; | ||||
| 	private final Vec3i blockInWorld = new Vec3i(); | ||||
| 	 | ||||
| 	public PacketSetBlock() { | ||||
| 		this("Core:SetBlock"); | ||||
| 	} | ||||
| 	 | ||||
| 	protected PacketSetBlock(String id) { | ||||
| 		super(id); | ||||
| 	} | ||||
| 	 | ||||
| 	public void set(BlockData block, Vec3i blockInWorld) { | ||||
| 		this.id = block.getId(); | ||||
| 		this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void read(DataInput input) throws IOException, DecodingException { | ||||
| 		this.id = input.readUTF(); | ||||
| 		this.blockInWorld.set(input.readInt(), input.readInt(), input.readInt()); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void write(DataOutput output) throws IOException { | ||||
| 		output.writeUTF(this.id); | ||||
| 		output.writeInt(this.blockInWorld.x); | ||||
| 		output.writeInt(this.blockInWorld.y); | ||||
| 		output.writeInt(this.blockInWorld.z); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void apply(WorldData world) { | ||||
| 		BlockData block = BlockDataRegistry.getInstance().get(id); | ||||
| 		world.setBlock(blockInWorld, block, true); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void getAffectedChunk(Vec3i output) { | ||||
| 		Coordinates.convertInWorldToChunk(this.blockInWorld, output); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -2,19 +2,25 @@ package ru.windcorp.progressia.common.world.entity; | ||||
|  | ||||
| import glm.vec._2.Vec2; | ||||
| import glm.vec._3.Vec3; | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.jputil.chars.StringUtil; | ||||
| import ru.windcorp.progressia.common.collision.Collideable; | ||||
| import ru.windcorp.progressia.common.collision.CollisionModel; | ||||
| import ru.windcorp.progressia.common.state.StatefulObject; | ||||
| import ru.windcorp.progressia.common.world.Coordinates; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericEntity; | ||||
|  | ||||
| public class EntityData extends StatefulObject implements Collideable { | ||||
| public class EntityData extends StatefulObject implements Collideable, GenericEntity { | ||||
| 	 | ||||
| 	private final Vec3 position = new Vec3(); | ||||
| 	private final Vec3 velocity = new Vec3(); | ||||
| 	 | ||||
| 	private final Vec2 direction = new Vec2(); | ||||
| 	 | ||||
| 	/** | ||||
| 	 * The unique {@code long} value guaranteed to never be assigned to an entity as its entity ID. | ||||
| 	 * This can safely be used as a placeholder or a sentinel value. | ||||
| 	 */ | ||||
| 	public static final long NULL_ENTITY_ID = 0x0000_0000_0000_0000; | ||||
| 	 | ||||
| 	private long entityId; | ||||
| 	 | ||||
| 	private CollisionModel collisionModel = null; | ||||
| @@ -25,20 +31,11 @@ public class EntityData extends StatefulObject implements Collideable { | ||||
| 		super(EntityDataRegistry.getInstance(), id); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public Vec3 getPosition() { | ||||
| 		return position; | ||||
| 	} | ||||
| 	 | ||||
| 	public Vec3i getBlockInWorld(Vec3i output) { | ||||
| 		if (output == null) output = new Vec3i(); | ||||
| 		return position.round(output); | ||||
| 	} | ||||
| 	 | ||||
| 	public Vec3i getChunkCoords(Vec3i output) { | ||||
| 		output = getBlockInWorld(output); | ||||
| 		return Coordinates.convertInWorldToChunk(output, output); | ||||
| 	} | ||||
| 	 | ||||
| 	public void setPosition(Vec3 position) { | ||||
| 		move(position.sub_(getPosition())); | ||||
| 	} | ||||
| @@ -79,6 +76,9 @@ public class EntityData extends StatefulObject implements Collideable { | ||||
| 	} | ||||
| 	 | ||||
| 	public void setEntityId(long entityId) { | ||||
| 		if (entityId == NULL_ENTITY_ID) { | ||||
| 			throw new IllegalArgumentException("Attempted to set entity ID to NULL_ENTITY_ID (" + entityId + ")"); | ||||
| 		} | ||||
| 		this.entityId = entityId; | ||||
| 	} | ||||
| 	 | ||||
| @@ -138,4 +138,17 @@ public class EntityData extends StatefulObject implements Collideable { | ||||
| 		return output; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public String toString() { | ||||
| 		return new StringBuilder(super.toString()) | ||||
| 				.append(" (EntityID ") | ||||
| 				.append(StringUtil.toFullHex(getEntityId())) | ||||
| 				.append(")") | ||||
| 				.toString(); | ||||
| 	} | ||||
| 	 | ||||
| 	public static String formatEntityId(long entityId) { | ||||
| 		return new String(StringUtil.toFullHex(entityId)); | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -4,10 +4,11 @@ import java.io.DataInput; | ||||
| import java.io.DataOutput; | ||||
| import java.io.IOException; | ||||
|  | ||||
| import ru.windcorp.progressia.common.comms.packets.PacketWorldChange; | ||||
| import ru.windcorp.progressia.common.state.IOContext; | ||||
| import ru.windcorp.progressia.common.util.DataBuffer; | ||||
| import ru.windcorp.progressia.common.util.crash.CrashReports; | ||||
| import ru.windcorp.progressia.common.world.DecodingException; | ||||
| import ru.windcorp.progressia.common.world.PacketWorldChange; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
|  | ||||
| public class PacketEntityChange extends PacketWorldChange { | ||||
| @@ -43,6 +44,28 @@ public class PacketEntityChange extends PacketWorldChange { | ||||
| 		return buffer.getWriter(); | ||||
| 	} | ||||
| 	 | ||||
| 	public void set(EntityData entity) { | ||||
| 		this.entityId = entity.getEntityId(); | ||||
| 		try { | ||||
| 			entity.write(this.buffer.getWriter(), IOContext.COMMS); | ||||
| 		} catch (IOException e) { | ||||
| 			CrashReports.report(e, "Entity could not be written"); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void read(DataInput input) throws IOException, DecodingException { | ||||
| 		this.entityId = input.readLong(); | ||||
| 		this.buffer.fill(input, input.readInt()); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void write(DataOutput output) throws IOException { | ||||
| 		output.writeLong(this.entityId); | ||||
| 		output.writeInt(this.buffer.getSize()); | ||||
| 		this.buffer.flush(output); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void apply(WorldData world) { | ||||
| 		EntityData entity = world.getEntity(getEntityId()); | ||||
|   | ||||
| @@ -0,0 +1,41 @@ | ||||
| package ru.windcorp.progressia.common.world.entity; | ||||
|  | ||||
| import java.io.DataInput; | ||||
| import java.io.DataOutput; | ||||
| import java.io.IOException; | ||||
|  | ||||
| import ru.windcorp.progressia.common.world.PacketWorldChange; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
|  | ||||
| public class PacketRevokeEntity extends PacketWorldChange { | ||||
| 	 | ||||
| 	private long entityId; | ||||
| 	 | ||||
| 	public PacketRevokeEntity() { | ||||
| 		this("Core:RevokeEntity"); | ||||
| 	} | ||||
|  | ||||
| 	protected PacketRevokeEntity(String id) { | ||||
| 		super(id); | ||||
| 	} | ||||
| 	 | ||||
| 	public void set(long entityId) { | ||||
| 		this.entityId = entityId; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void read(DataInput input) throws IOException { | ||||
| 		this.entityId = input.readLong(); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void write(DataOutput output) throws IOException { | ||||
| 		output.writeLong(this.entityId); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void apply(WorldData world) { | ||||
| 		world.removeEntity(this.entityId); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,113 @@ | ||||
| package ru.windcorp.progressia.common.world.generic; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.function.BiConsumer; | ||||
| import java.util.function.BiFunction; | ||||
| import java.util.function.BiPredicate; | ||||
| import glm.vec._3.i.Vec3i; | ||||
|  | ||||
| public interface ChunkMap<V> { | ||||
| 	 | ||||
| 	/* | ||||
| 	 * Size | ||||
| 	 */ | ||||
| 	 | ||||
| 	int size(); | ||||
| 	 | ||||
| 	default boolean isEmpty() { | ||||
| 		return size() == 0; | ||||
| 	} | ||||
| 	 | ||||
| 	/* | ||||
| 	 * Basic operations | ||||
| 	 */ | ||||
| 	 | ||||
| 	boolean containsKey(Vec3i pos); | ||||
| 	 | ||||
| 	V get(Vec3i pos); | ||||
| 	V put(Vec3i pos, V obj); | ||||
| 	V remove(Vec3i pos); | ||||
| 	 | ||||
| 	default boolean containsValue(V value) { | ||||
| 		return values().contains(value); | ||||
| 	} | ||||
| 	 | ||||
| 	default V getOrDefault(Vec3i pos, V def) { | ||||
| 		return containsKey(pos) ? def : get(pos); | ||||
| 	} | ||||
| 	 | ||||
| 	default V compute(Vec3i pos, BiFunction<? super Vec3i, ? super V, ? extends V> remappingFunction) { | ||||
| 		V newValue = remappingFunction.apply(pos, get(pos)); | ||||
| 		 | ||||
| 		if (newValue == null) { | ||||
| 			remove(pos); | ||||
| 		} else { | ||||
| 			put(pos, newValue); | ||||
| 		} | ||||
| 		 | ||||
| 		return newValue; | ||||
| 	} | ||||
| 	 | ||||
| 	// TODO implement ALL default methods from Map | ||||
| 	 | ||||
| 	/* | ||||
| 	 * Basic operation wrappers | ||||
| 	 */ | ||||
| 	 | ||||
| 	// TODO implement (int, int, int) and GenericChunk versions of all of the above | ||||
| 	 | ||||
| 	default boolean containsChunk(GenericChunk<?, ?, ?, ?> chunk) { | ||||
| 		return containsKey(chunk.getPosition()); | ||||
| 	} | ||||
| 	 | ||||
| 	default V get(GenericChunk<?, ?, ?, ?> chunk) { | ||||
| 		return get(chunk.getPosition()); | ||||
| 	} | ||||
| 	 | ||||
| 	default V put(GenericChunk<?, ?, ?, ?> chunk, V obj) { | ||||
| 		return put(chunk.getPosition(), obj); | ||||
| 	} | ||||
| 	 | ||||
| 	default V remove(GenericChunk<?, ?, ?, ?> chunk) { | ||||
| 		return remove(chunk.getPosition()); | ||||
| 	} | ||||
| 	 | ||||
| 	default V getOrDefault(GenericChunk<?, ?, ?, ?> chunk, V def) { | ||||
| 		return containsChunk(chunk) ? def : get(chunk); | ||||
| 	} | ||||
| 	 | ||||
| 	default <C extends GenericChunk<C, ?, ?, ?>> V compute(C chunk, BiFunction<? super C, ? super V, ? extends V> remappingFunction) { | ||||
| 		V newValue = remappingFunction.apply(chunk, get(chunk)); | ||||
| 		 | ||||
| 		if (newValue == null) { | ||||
| 			remove(chunk); | ||||
| 		} else { | ||||
| 			put(chunk, newValue); | ||||
| 		} | ||||
| 		 | ||||
| 		return newValue; | ||||
| 	} | ||||
| 	 | ||||
| 	/* | ||||
| 	 * Views | ||||
| 	 */ | ||||
| 	 | ||||
| 	Collection<V> values(); | ||||
| 	ChunkSet keys(); | ||||
| 	 | ||||
| 	/* | ||||
| 	 * Bulk operations | ||||
| 	 */ | ||||
| 	 | ||||
| 	boolean removeIf(BiPredicate<? super Vec3i, ? super V> condition); | ||||
| 	void forEach(BiConsumer<? super Vec3i, ? super V> action); | ||||
| 	 | ||||
| 	default <C extends GenericChunk<C, ?, ?, ?>> void forEachIn(GenericWorld<?, ?, ?, C, ?> world, BiConsumer<? super C, ? super V> action) { | ||||
| 		forEach((pos, value) -> { | ||||
| 			C chunk = world.getChunk(pos); | ||||
| 			if (chunk == null) return; | ||||
| 			action.accept(chunk, value); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,212 @@ | ||||
| package ru.windcorp.progressia.common.world.generic; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.Iterator; | ||||
| import java.util.Set; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.function.Predicate; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import gnu.trove.set.hash.TLongHashSet; | ||||
| import ru.windcorp.progressia.common.util.Vectors; | ||||
|  | ||||
| public interface ChunkSet extends Iterable<Vec3i> { | ||||
| 	 | ||||
| 	/* | ||||
| 	 * Size | ||||
| 	 */ | ||||
| 	 | ||||
| 	int size(); | ||||
| 	 | ||||
| 	default boolean isEmpty() { | ||||
| 		return size() == 0; | ||||
| 	} | ||||
| 	 | ||||
| 	/* | ||||
| 	 * Basic operations | ||||
| 	 */ | ||||
| 	 | ||||
| 	boolean contains(Vec3i pos); | ||||
| 	boolean add(Vec3i pos); | ||||
| 	boolean remove(Vec3i pos); | ||||
| 	 | ||||
| 	/* | ||||
| 	 * Basic operation wrappers | ||||
| 	 */ | ||||
| 	 | ||||
| 	default boolean contains(int x, int y, int z) { | ||||
| 		Vec3i v = Vectors.grab3i(); | ||||
| 		boolean result = contains(v); | ||||
| 		Vectors.release(v); | ||||
| 		return result; | ||||
| 	} | ||||
| 	 | ||||
| 	default boolean add(int x, int y, int z) { | ||||
| 		Vec3i v = Vectors.grab3i(); | ||||
| 		boolean result = add(v); | ||||
| 		Vectors.release(v); | ||||
| 		return result; | ||||
| 	} | ||||
| 	 | ||||
| 	default boolean remove(int x, int y, int z) { | ||||
| 		Vec3i v = Vectors.grab3i(); | ||||
| 		boolean result = remove(v); | ||||
| 		Vectors.release(v); | ||||
| 		return result; | ||||
| 	} | ||||
| 	 | ||||
| 	default boolean contains(GenericChunk<?, ?, ?, ?> chunk) { | ||||
| 		return contains(chunk.getPosition()); | ||||
| 	} | ||||
| 	 | ||||
| 	default boolean add(GenericChunk<?, ?, ?, ?> chunk) { | ||||
| 		return add(chunk.getPosition()); | ||||
| 	} | ||||
| 	 | ||||
| 	default boolean remove(GenericChunk<?, ?, ?, ?> chunk) { | ||||
| 		return remove(chunk.getPosition()); | ||||
| 	} | ||||
| 	 | ||||
| 	default <C extends GenericChunk<C, ?, ?, ?>> void forEachIn(GenericWorld<?, ?, ?, C, ?> world, Consumer<? super C> action) { | ||||
| 		forEach(position -> { | ||||
| 			C chunk = world.getChunk(position); | ||||
| 			if (chunk == null) return; | ||||
| 			action.accept(chunk); | ||||
| 		}); | ||||
| 	} | ||||
| 	 | ||||
| 	/* | ||||
| 	 * Bulk operations on ChunkSets | ||||
| 	 */ | ||||
| 	 | ||||
| 	boolean containsAll(ChunkSet other); | ||||
| 	boolean containsAny(ChunkSet other); | ||||
| 	 | ||||
| 	void addAll(ChunkSet other); | ||||
| 	void removeAll(ChunkSet other); | ||||
| 	void retainAll(ChunkSet other); | ||||
| 	 | ||||
| 	/* | ||||
| 	 * Other bulk operations | ||||
| 	 */ | ||||
| 	 | ||||
| 	void clear(); | ||||
| 	 | ||||
| 	default boolean containsAll(Iterable<? extends Vec3i> other) { | ||||
| 		boolean[] hasMissing = new boolean[] { false }; | ||||
| 		 | ||||
| 		other.forEach(v -> { | ||||
| 			if (!contains(v)) { | ||||
| 				hasMissing[0] = true; | ||||
| 			} | ||||
| 		}); | ||||
| 		 | ||||
| 		return hasMissing[0]; | ||||
| 	} | ||||
| 	 | ||||
| 	default boolean containsAny(Iterable<? extends Vec3i> other) { | ||||
| 		boolean[] hasPresent = new boolean[] { false }; | ||||
| 		 | ||||
| 		other.forEach(v -> { | ||||
| 			if (contains(v)) { | ||||
| 				hasPresent[0] = true; | ||||
| 			} | ||||
| 		}); | ||||
| 		 | ||||
| 		return hasPresent[0]; | ||||
| 	} | ||||
| 	 | ||||
| 	default void addAll(Iterable<? extends Vec3i> other) { | ||||
| 		other.forEach(this::add); | ||||
| 	} | ||||
| 	 | ||||
| 	default void removeAll(Iterable<? extends Vec3i> other) { | ||||
| 		other.forEach(this::remove); | ||||
| 	} | ||||
| 	 | ||||
| 	default void retainAll(Iterable<? extends Vec3i> other) { | ||||
| 		if (other instanceof ChunkSet) { | ||||
| 			// We shouldn't invoke retainAll(ChunkSet) because we could be the fallback for it | ||||
| 			removeIf(v -> !((ChunkSet) other).contains(v)); | ||||
| 			return; | ||||
| 		} | ||||
| 		 | ||||
| 		final int threshold = 16; // Maximum size of other at which point creating a Set becomes faster than iterating | ||||
| 		 | ||||
| 		Collection<? extends Vec3i> collection = null; | ||||
| 		int otherSize = -1; | ||||
| 		 | ||||
| 		if (other instanceof Set<?>) { | ||||
| 			collection = (Set<? extends Vec3i>) other; | ||||
| 		} else if (other instanceof Collection<?>) { | ||||
| 			Collection<? extends Vec3i> otherAsCollection = ((Collection<? extends Vec3i>) other); | ||||
| 			otherSize = otherAsCollection.size(); | ||||
| 			 | ||||
| 			if (otherSize < threshold) { | ||||
| 				collection = otherAsCollection; | ||||
| 			} | ||||
| 		} | ||||
| 		 | ||||
| 		if (collection != null) { | ||||
| 			final Collection<? extends Vec3i> c = collection; | ||||
| 			removeIf(v -> !c.contains(v)); | ||||
| 			return; | ||||
| 		} | ||||
| 		 | ||||
| 		if (otherSize < 0) { | ||||
| 			otherSize = gnu.trove.impl.Constants.DEFAULT_CAPACITY; | ||||
| 		} | ||||
| 		 | ||||
| 		retainAll(new LongBasedChunkSet(new TLongHashSet(otherSize), other)); | ||||
| 		return; | ||||
| 	} | ||||
| 	 | ||||
| 	default void removeIf(Predicate<? super Vec3i> condition) { | ||||
| 		for (Iterator<? extends Vec3i> it = iterator(); it.hasNext();) { | ||||
| 			if (condition.test(it.next())) { | ||||
| 				it.remove(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	default void retainIf(Predicate<? super Vec3i> condition) { | ||||
| 		for (Iterator<? extends Vec3i> it = iterator(); it.hasNext();) { | ||||
| 			if (!condition.test(it.next())) { | ||||
| 				it.remove(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	default boolean containsAllChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) { | ||||
| 		boolean[] hasMissing = new boolean[] { false }; | ||||
| 		 | ||||
| 		chunks.forEach(c -> { | ||||
| 			if (!contains(c.getPosition())) { | ||||
| 				hasMissing[0] = true; | ||||
| 			} | ||||
| 		}); | ||||
| 		 | ||||
| 		return hasMissing[0]; | ||||
| 	} | ||||
| 	 | ||||
| 	default boolean containsAnyChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) { | ||||
| 		boolean[] hasPresent = new boolean[] { false }; | ||||
| 		 | ||||
| 		chunks.forEach(c -> { | ||||
| 			if (contains(c.getPosition())) { | ||||
| 				hasPresent[0] = true; | ||||
| 			} | ||||
| 		}); | ||||
| 		 | ||||
| 		return hasPresent[0]; | ||||
| 	} | ||||
| 	 | ||||
| 	default void addAllChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) { | ||||
| 		chunks.forEach(this::add); | ||||
| 	} | ||||
| 	 | ||||
| 	default void removeAllChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) { | ||||
| 		chunks.forEach(this::remove); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,270 @@ | ||||
| package ru.windcorp.progressia.common.world.generic; | ||||
|  | ||||
| import java.util.Iterator; | ||||
| import java.util.NoSuchElementException; | ||||
| import java.util.Objects; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.function.Predicate; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import gnu.trove.set.hash.TLongHashSet; | ||||
|  | ||||
| public class ChunkSets { | ||||
| 	 | ||||
| 	public static ChunkSet newHashSet() { | ||||
| 		return new LongBasedChunkSet(new TLongHashSet()); | ||||
| 	} | ||||
| 	 | ||||
| 	public static ChunkSet newSyncHashSet(Object mutex) { | ||||
| 		return new SynchronizedChunkSet(new LongBasedChunkSet(new TLongHashSet()), mutex); | ||||
| 	} | ||||
| 	 | ||||
| 	public static ChunkSet newSyncHashSet() { | ||||
| 		return newSyncHashSet(null); | ||||
| 	} | ||||
| 	 | ||||
| 	public static ChunkSet empty() { | ||||
| 		return EMPTY_SET; | ||||
| 	} | ||||
| 	 | ||||
| 	private ChunkSets() {} | ||||
| 	 | ||||
| 	private final static ChunkSet EMPTY_SET = new ChunkSet() { | ||||
|  | ||||
| 		@Override | ||||
| 		public Iterator<Vec3i> iterator() { | ||||
| 			return new Iterator<Vec3i>() { | ||||
| 				@Override | ||||
| 				public boolean hasNext() { | ||||
| 					return false; | ||||
| 				} | ||||
| 				@Override | ||||
| 				public Vec3i next() { | ||||
| 					throw new NoSuchElementException(); | ||||
| 				} | ||||
| 			}; | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public int size() { | ||||
| 			return 0; | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean contains(Vec3i pos) { | ||||
| 			return false; | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean add(Vec3i pos) { | ||||
| 			throw new UnsupportedOperationException(); | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean remove(Vec3i pos) { | ||||
| 			throw new UnsupportedOperationException(); | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean containsAll(ChunkSet other) { | ||||
| 			return false; | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean containsAny(ChunkSet other) { | ||||
| 			return false; | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public void addAll(ChunkSet other) { | ||||
| 			throw new UnsupportedOperationException(); | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public void removeAll(ChunkSet other) { | ||||
| 			throw new UnsupportedOperationException(); | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public void retainAll(ChunkSet other) { | ||||
| 			throw new UnsupportedOperationException(); | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public void clear() { | ||||
| 			throw new UnsupportedOperationException(); | ||||
| 		} | ||||
| 		 | ||||
| 	}; | ||||
| 	 | ||||
| 	private static class SynchronizedChunkSet implements ChunkSet { | ||||
| 		 | ||||
| 		private final ChunkSet parent; | ||||
| 		private final Object mutex; | ||||
|  | ||||
| 		public SynchronizedChunkSet(ChunkSet parent, Object mutex) { | ||||
| 			Objects.requireNonNull(parent, "parent"); | ||||
| 			this.parent = parent; | ||||
| 			 | ||||
| 			this.mutex = mutex == null ? this : mutex; | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public Iterator<Vec3i> iterator() { | ||||
| 			return parent.iterator(); // Must be synchronized manually by user! | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public void forEach(Consumer<? super Vec3i> action) { | ||||
| 			synchronized (mutex) { parent.forEach(action); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public int size() { | ||||
| 			synchronized (mutex) { return parent.size(); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean isEmpty() { | ||||
| 			synchronized (mutex) { return parent.isEmpty(); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean contains(Vec3i pos) { | ||||
| 			synchronized (mutex) { return parent.contains(pos); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean add(Vec3i pos) { | ||||
| 			synchronized (mutex) { return parent.add(pos); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean remove(Vec3i pos) { | ||||
| 			synchronized (mutex) { return parent.remove(pos); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean contains(int x, int y, int z) { | ||||
| 			synchronized (mutex) { return parent.contains(x, y, z); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean add(int x, int y, int z) { | ||||
| 			synchronized (mutex) { return parent.add(x, y, z); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean remove(int x, int y, int z) { | ||||
| 			synchronized (mutex) { return parent.remove(x, y, z); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean contains(GenericChunk<?, ?, ?, ?> chunk) { | ||||
| 			synchronized (mutex) { return parent.contains(chunk); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean add(GenericChunk<?, ?, ?, ?> chunk) { | ||||
| 			synchronized (mutex) { return parent.add(chunk); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean remove(GenericChunk<?, ?, ?, ?> chunk) { | ||||
| 			synchronized (mutex) { return parent.remove(chunk); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public <C extends GenericChunk<C, ?, ?, ?>> void forEachIn(GenericWorld<?, ?, ?, C, ?> world, | ||||
| 				Consumer<? super C> action) { | ||||
| 			synchronized (mutex) { parent.forEachIn(world, action); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean containsAll(ChunkSet other) { | ||||
| 			synchronized (mutex) { return parent.containsAll(other); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean containsAny(ChunkSet other) { | ||||
| 			synchronized (mutex) { return parent.containsAny(other); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public void addAll(ChunkSet other) { | ||||
| 			synchronized (mutex) { parent.addAll(other); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public void removeAll(ChunkSet other) { | ||||
| 			synchronized (mutex) { parent.removeAll(other); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public void retainAll(ChunkSet other) { | ||||
| 			synchronized (mutex) { parent.retainAll(other); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public void clear() { | ||||
| 			synchronized (mutex) { parent.clear(); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean containsAll(Iterable<? extends Vec3i> other) { | ||||
| 			synchronized (mutex) { return parent.containsAll(other); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean containsAny(Iterable<? extends Vec3i> other) { | ||||
| 			synchronized (mutex) { return parent.containsAny(other); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public void addAll(Iterable<? extends Vec3i> other) { | ||||
| 			synchronized (mutex) { parent.addAll(other); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public void removeAll(Iterable<? extends Vec3i> other) { | ||||
| 			synchronized (mutex) { parent.removeAll(other); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public void retainAll(Iterable<? extends Vec3i> other) { | ||||
| 			synchronized (mutex) { parent.retainAll(other); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public void removeIf(Predicate<? super Vec3i> condition) { | ||||
| 			synchronized (mutex) { parent.removeIf(condition); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public void retainIf(Predicate<? super Vec3i> condition) { | ||||
| 			synchronized (mutex) { parent.retainIf(condition); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean containsAllChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) { | ||||
| 			synchronized (mutex) { return parent.containsAllChunks(chunks); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean containsAnyChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) { | ||||
| 			synchronized (mutex) { return parent.containsAnyChunks(chunks); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public void addAllChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) { | ||||
| 			synchronized (mutex) { parent.addAllChunks(chunks); } | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public void removeAllChunks(Iterable<? extends GenericChunk<?, ?, ?, ?>> chunks) { | ||||
| 			synchronized (mutex) { parent.removeAllChunks(chunks); } | ||||
| 		} | ||||
| 		 | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| package ru.windcorp.progressia.common.world.generic; | ||||
|  | ||||
| public interface GenericBlock { | ||||
| 	 | ||||
| 	String getId(); | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,85 @@ | ||||
| package ru.windcorp.progressia.common.world.generic; | ||||
|  | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.util.VectorUtil; | ||||
| import ru.windcorp.progressia.common.util.Vectors; | ||||
| import ru.windcorp.progressia.common.world.Coordinates; | ||||
| import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
|  | ||||
| public interface GenericChunk< | ||||
| 	Self extends GenericChunk<Self, B, T, TS>, | ||||
| 	B    extends GenericBlock, | ||||
| 	T    extends GenericTile, | ||||
| 	TS   extends GenericTileStack<TS, T, Self> | ||||
| > { | ||||
| 	 | ||||
| 	public static final int BLOCKS_PER_CHUNK = Coordinates.CHUNK_SIZE; | ||||
|  | ||||
| 	Vec3i getPosition(); | ||||
| 	 | ||||
| 	B getBlock(Vec3i blockInChunk); | ||||
| 	TS getTiles(Vec3i blockInChunk, BlockFace face); | ||||
| 	boolean hasTiles(Vec3i blockInChunk, BlockFace face); | ||||
| 	 | ||||
| 	default int getX() { | ||||
| 		return getPosition().x; | ||||
| 	} | ||||
| 	 | ||||
| 	default int getY() { | ||||
| 		return getPosition().y; | ||||
| 	} | ||||
| 	 | ||||
| 	default int getZ() { | ||||
| 		return getPosition().z; | ||||
| 	} | ||||
| 	 | ||||
| 	default boolean containsBiC(Vec3i blockInChunk) { | ||||
| 		return | ||||
| 				blockInChunk.x >= 0 && blockInChunk.x < BLOCKS_PER_CHUNK && | ||||
| 				blockInChunk.y >= 0 && blockInChunk.y < BLOCKS_PER_CHUNK && | ||||
| 				blockInChunk.z >= 0 && blockInChunk.z < BLOCKS_PER_CHUNK; | ||||
| 	} | ||||
| 	 | ||||
| 	default boolean containsBiW(Vec3i blockInWorld) { | ||||
| 		Vec3i v = Vectors.grab3i(); | ||||
| 		 | ||||
| 		v = Coordinates.getInWorld(getPosition(), Vectors.ZERO_3i, v); | ||||
| 		v = blockInWorld.sub(v, v); | ||||
| 		 | ||||
| 		boolean result = containsBiC(v); | ||||
| 		 | ||||
| 		Vectors.release(v); | ||||
| 		return result; | ||||
| 	} | ||||
| 	 | ||||
| 	default void forEachBiC(Consumer<? super Vec3i> action) { | ||||
| 		VectorUtil.iterateCuboid( | ||||
| 				0,                0,                0, | ||||
| 				BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, | ||||
| 				action | ||||
| 		); | ||||
| 	} | ||||
| 	 | ||||
| 	default void forEachBiW(Consumer<? super Vec3i> action) { | ||||
| 		VectorUtil.iterateCuboid( | ||||
| 				Coordinates.getInWorld(getX(), 0), | ||||
| 				Coordinates.getInWorld(getY(), 0), | ||||
| 				Coordinates.getInWorld(getZ(), 0), | ||||
| 				BLOCKS_PER_CHUNK, | ||||
| 				BLOCKS_PER_CHUNK, | ||||
| 				BLOCKS_PER_CHUNK, | ||||
| 				action | ||||
| 		); | ||||
| 	} | ||||
| 	 | ||||
| 	default TS getTilesOrNull(Vec3i blockInChunk, BlockFace face) { | ||||
| 		if (hasTiles(blockInChunk, face)) { | ||||
| 			return getTiles(blockInChunk, face); | ||||
| 		} | ||||
| 		 | ||||
| 		return null; | ||||
| 	} | ||||
| 	 | ||||
| } | ||||
| @@ -0,0 +1,22 @@ | ||||
| package ru.windcorp.progressia.common.world.generic; | ||||
|  | ||||
| import glm.vec._3.Vec3; | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.world.Coordinates; | ||||
|  | ||||
| public interface GenericEntity { | ||||
| 	 | ||||
| 	String getId(); | ||||
| 	Vec3 getPosition(); | ||||
| 	 | ||||
| 	default Vec3i getBlockInWorld(Vec3i output) { | ||||
| 		if (output == null) output = new Vec3i(); | ||||
| 		return getPosition().round(output); | ||||
| 	} | ||||
| 	 | ||||
| 	default Vec3i getChunkCoords(Vec3i output) { | ||||
| 		output = getBlockInWorld(output); | ||||
| 		return Coordinates.convertInWorldToChunk(output, output); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| package ru.windcorp.progressia.common.world.generic; | ||||
|  | ||||
| public interface GenericTile { | ||||
| 	 | ||||
| 	String getId(); | ||||
|  | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| package ru.windcorp.progressia.common.world.tile; | ||||
| package ru.windcorp.progressia.common.world.generic; | ||||
| 
 | ||||
| import java.util.AbstractList; | ||||
| import java.util.Objects; | ||||
| @@ -6,11 +6,14 @@ import java.util.RandomAccess; | ||||
| import java.util.function.Consumer; | ||||
| 
 | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.util.namespaces.Namespaced; | ||||
| import ru.windcorp.progressia.common.world.Coordinates; | ||||
| import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
| 
 | ||||
| public abstract class GenericTileStack<T extends Namespaced, C> | ||||
| public abstract class GenericTileStack< | ||||
| 	Self extends GenericTileStack<Self, T, C>, | ||||
| 	T    extends GenericTile, | ||||
| 	C    extends GenericChunk<C, ?, T, Self> | ||||
| > | ||||
| extends AbstractList<T> | ||||
| implements RandomAccess { | ||||
| 	 | ||||
| @@ -21,13 +24,12 @@ implements RandomAccess { | ||||
| 	public static final int TILES_PER_FACE = 8; | ||||
| 	 | ||||
| 	public abstract Vec3i getBlockInChunk(Vec3i output); | ||||
| 	protected abstract Vec3i getChunkPos(); | ||||
| 	public abstract C getChunk(); | ||||
| 	public abstract BlockFace getFace(); | ||||
| 	 | ||||
| 	public Vec3i getBlockInWorld(Vec3i output) { | ||||
| 		// This is safe | ||||
| 		return Coordinates.getInWorld(getChunkPos(), getBlockInChunk(output), output); | ||||
| 		return Coordinates.getInWorld(getChunk().getPosition(), getBlockInChunk(output), output); | ||||
| 	} | ||||
| 	 | ||||
| 	public boolean isFull() { | ||||
| @@ -0,0 +1,176 @@ | ||||
| package ru.windcorp.progressia.common.world.generic; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import glm.Glm; | ||||
| import glm.vec._3.Vec3; | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.util.Vectors; | ||||
| import ru.windcorp.progressia.common.world.Coordinates; | ||||
| import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
|  | ||||
| public interface GenericWorld< | ||||
| 	B  extends GenericBlock, | ||||
| 	T  extends GenericTile, | ||||
| 	TS extends GenericTileStack<TS, T, C>, | ||||
| 	C  extends GenericChunk<C, B, T, TS>, | ||||
| 	E  extends GenericEntity | ||||
| > { | ||||
|  | ||||
| 	Collection<C> getChunks(); | ||||
| 	C getChunk(Vec3i pos); | ||||
| 	 | ||||
| 	Collection<E> getEntities(); | ||||
| 	 | ||||
| 	/* | ||||
| 	 * Chunks | ||||
| 	 */ | ||||
| 	 | ||||
| 	default C getChunkByBlock(Vec3i blockInWorld) { | ||||
| 		Vec3i chunkCoords = Vectors.grab3i(); | ||||
| 		chunkCoords = Coordinates.convertInWorldToChunk(blockInWorld, chunkCoords); | ||||
| 		C result = getChunk(chunkCoords); | ||||
| 		Vectors.release(chunkCoords); | ||||
| 		return result; | ||||
| 	} | ||||
| 	 | ||||
| 	default B getBlock(Vec3i blockInWorld) { | ||||
| 		Vec3i v = Vectors.grab3i(); | ||||
| 		B result; | ||||
| 		 | ||||
| 		C chunk = getChunk(Coordinates.convertInWorldToChunk(blockInWorld, v)); | ||||
| 		if (chunk == null) { | ||||
| 			result = null; | ||||
| 		} else { | ||||
| 			result = chunk.getBlock(Coordinates.convertInWorldToInChunk(blockInWorld, v)); | ||||
| 		} | ||||
| 		 | ||||
| 		Vectors.release(v); | ||||
| 		return result; | ||||
| 	} | ||||
| 	 | ||||
| 	default TS getTiles(Vec3i blockInWorld, BlockFace face) { | ||||
| 		Vec3i v = Vectors.grab3i(); | ||||
| 		TS result; | ||||
| 		 | ||||
| 		C chunk = getChunk(Coordinates.convertInWorldToChunk(blockInWorld, v)); | ||||
| 		if (chunk == null) { | ||||
| 			result = null; | ||||
| 		} else { | ||||
| 			result = chunk.getTiles(Coordinates.convertInWorldToInChunk(blockInWorld, v), face); | ||||
| 		} | ||||
| 		 | ||||
| 		Vectors.release(v); | ||||
| 		return result; | ||||
| 	} | ||||
| 	 | ||||
| 	default TS getTilesOrNull(Vec3i blockInWorld, BlockFace face) { | ||||
| 		Vec3i v = Vectors.grab3i(); | ||||
| 		TS result; | ||||
| 		 | ||||
| 		C chunk = getChunk(Coordinates.convertInWorldToChunk(blockInWorld, v)); | ||||
| 		if (chunk == null) { | ||||
| 			result = null; | ||||
| 		} else { | ||||
| 			result = chunk.getTilesOrNull(Coordinates.convertInWorldToInChunk(blockInWorld, v), face); | ||||
| 		} | ||||
| 		 | ||||
| 		Vectors.release(v); | ||||
| 		return result; | ||||
| 	} | ||||
| 	 | ||||
| 	default boolean hasTiles(Vec3i blockInWorld, BlockFace face) { | ||||
| 		Vec3i v = Vectors.grab3i(); | ||||
| 		boolean result; | ||||
| 		 | ||||
| 		C chunk = getChunk(Coordinates.convertInWorldToChunk(blockInWorld, v)); | ||||
| 		if (chunk == null) { | ||||
| 			result = false; | ||||
| 		} else { | ||||
| 			result = chunk.hasTiles(Coordinates.convertInWorldToInChunk(blockInWorld, v), face); | ||||
| 		} | ||||
| 		 | ||||
| 		Vectors.release(v); | ||||
| 		return result; | ||||
| 	} | ||||
| 	 | ||||
| 	default T getTile(Vec3i blockInWorld, BlockFace face, int layer) { | ||||
| 		TS stack = getTilesOrNull(blockInWorld, face); | ||||
| 		if (stack == null || stack.size() <= layer) return null; | ||||
| 		return stack.get(layer); | ||||
| 	} | ||||
| 	 | ||||
| 	default boolean isChunkLoaded(Vec3i pos) { | ||||
| 		return getChunk(pos) != null; | ||||
| 	} | ||||
| 	 | ||||
| 	default boolean isBlockLoaded(Vec3i blockInWorld) { | ||||
| 		return getChunkByBlock(blockInWorld) != null; | ||||
| 	} | ||||
| 	 | ||||
| 	default void forEachChunk(Consumer<? super C> action) { | ||||
| 		getChunks().forEach(action); | ||||
| 	} | ||||
| 	 | ||||
| 	/* | ||||
| 	 * Entities | ||||
| 	 */ | ||||
| 	 | ||||
| 	default void forEachEntity(Consumer<? super E> action) { | ||||
| 		getEntities().forEach(action); | ||||
| 	} | ||||
| 	 | ||||
| 	default void forEachEntityIn(Vec3i min, Vec3i max, Consumer<? super E> action) { | ||||
| 		forEachEntity(e -> { | ||||
| 			Vec3 pos = e.getPosition(); | ||||
| 			if (pos.x < min.x || pos.y < min.y || pos.z < min.z || pos.x > max.x || pos.y > max.y || pos.z > max.z) { | ||||
| 				action.accept(e); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| 	 | ||||
| 	default void forEachEntityIn(Vec3 min, Vec3 max, Consumer<? super E> action) { | ||||
| 		forEachEntity(e -> { | ||||
| 			Vec3 pos = e.getPosition(); | ||||
| 			if (pos.x < min.x || pos.y < min.y || pos.z < min.z || pos.x > max.x || pos.y > max.y || pos.z > max.z) { | ||||
| 				action.accept(e); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| 	 | ||||
| 	default void forEachEntityInChunk(Vec3i pos, Consumer<? super E> action) { | ||||
| 		Vec3i v = Vectors.grab3i(); | ||||
| 		 | ||||
| 		forEachEntity(e -> { | ||||
| 			e.getChunkCoords(v); | ||||
| 			if (Glm.equals(v, pos)) { | ||||
| 				action.accept(e); | ||||
| 			} | ||||
| 		}); | ||||
| 		 | ||||
| 		Vectors.release(v); | ||||
| 	} | ||||
| 	 | ||||
| 	default void forEachEntityInChunk(C chunk, Consumer<? super E> action) { | ||||
| 		Vec3i v = Vectors.grab3i(); | ||||
| 		 | ||||
| 		forEachEntity(e -> { | ||||
| 			e.getChunkCoords(v); | ||||
| 			if (Glm.equals(v, chunk.getPosition())) { | ||||
| 				action.accept(e); | ||||
| 			} | ||||
| 		}); | ||||
| 		 | ||||
| 		Vectors.release(v); | ||||
| 	} | ||||
| 	 | ||||
| 	default void forEachEntityWithId(String id, Consumer<? super E> action) { | ||||
| 		forEachEntity(e -> { | ||||
| 			if (id.equals(e.getId())) { | ||||
| 				action.accept(e); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| 	 | ||||
| } | ||||
| @@ -0,0 +1,89 @@ | ||||
| package ru.windcorp.progressia.common.world.generic; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.function.BiConsumer; | ||||
| import java.util.function.BiPredicate; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import gnu.trove.map.TLongObjectMap; | ||||
| import ru.windcorp.progressia.common.util.CoordinatePacker; | ||||
| import ru.windcorp.progressia.common.util.Vectors; | ||||
|  | ||||
| public class LongBasedChunkMap<V> implements ChunkMap<V> { | ||||
| 	 | ||||
| 	protected final TLongObjectMap<V> impl; | ||||
| 	private final ChunkSet keys; | ||||
|  | ||||
| 	public LongBasedChunkMap(TLongObjectMap<V> impl) { | ||||
| 		this.impl = impl; | ||||
| 		this.keys = new LongBasedChunkSet(impl.keySet()); | ||||
| 	} | ||||
|  | ||||
| 	private static long getKey(Vec3i v) { | ||||
| 		return CoordinatePacker.pack3IntsIntoLong(v); | ||||
| 	} | ||||
| 	 | ||||
| 	private static Vec3i getVector(long key, Vec3i output) { | ||||
| 		return CoordinatePacker.unpack3IntsFromLong(key, output); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public int size() { | ||||
| 		return impl.size(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public boolean containsKey(Vec3i pos) { | ||||
| 		return impl.containsKey(getKey(pos)); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public V get(Vec3i pos) { | ||||
| 		return impl.get(getKey(pos)); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public V put(Vec3i pos, V obj) { | ||||
| 		return impl.put(getKey(pos), obj); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public V remove(Vec3i pos) { | ||||
| 		return impl.remove(getKey(pos)); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Collection<V> values() { | ||||
| 		return impl.valueCollection(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public ChunkSet keys() { | ||||
| 		return keys; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public boolean removeIf(BiPredicate<? super Vec3i, ? super V> condition) { | ||||
| 		Vec3i v = Vectors.grab3i(); | ||||
| 		 | ||||
| 		boolean result = impl.retainEntries((key, value) -> { | ||||
| 			return !condition.test(getVector(key, v), value); | ||||
| 		}); | ||||
| 		 | ||||
| 		Vectors.release(v); | ||||
| 		return result; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void forEach(BiConsumer<? super Vec3i, ? super V> action) { | ||||
| 		Vec3i v = Vectors.grab3i(); | ||||
| 		 | ||||
| 		impl.forEachEntry((key, value) -> { | ||||
| 			action.accept(getVector(key, v), value); | ||||
| 			return true; | ||||
| 		}); | ||||
| 		 | ||||
| 		Vectors.release(v); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,152 @@ | ||||
| package ru.windcorp.progressia.common.world.generic; | ||||
|  | ||||
| import java.util.Iterator; | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import gnu.trove.iterator.TLongIterator; | ||||
| import gnu.trove.set.TLongSet; | ||||
| import ru.windcorp.progressia.common.util.CoordinatePacker; | ||||
| import ru.windcorp.progressia.common.util.Vectors; | ||||
|  | ||||
| public class LongBasedChunkSet implements ChunkSet { | ||||
|  | ||||
| 	protected final TLongSet impl; | ||||
| 	 | ||||
| 	public LongBasedChunkSet(TLongSet impl) { | ||||
| 		this.impl = impl; | ||||
| 	} | ||||
| 	 | ||||
| 	public LongBasedChunkSet(TLongSet impl, ChunkSet copyFrom) { | ||||
| 		this(impl); | ||||
| 		addAll(copyFrom); | ||||
| 	} | ||||
| 	 | ||||
| 	public LongBasedChunkSet(TLongSet impl, Iterable<? extends Vec3i> copyFrom) { | ||||
| 		this(impl); | ||||
| 		addAll(copyFrom); | ||||
| 	} | ||||
| 	 | ||||
| 	public LongBasedChunkSet(TLongSet impl, GenericWorld<?, ?, ?, ?, ?> copyFrom) { | ||||
| 		this(impl); | ||||
| 		addAllChunks(copyFrom.getChunks()); | ||||
| 	} | ||||
|  | ||||
| 	private static long getKey(Vec3i v) { | ||||
| 		return CoordinatePacker.pack3IntsIntoLong(v); | ||||
| 	} | ||||
| 	 | ||||
| 	private static Vec3i getVector(long key, Vec3i output) { | ||||
| 		return CoordinatePacker.unpack3IntsFromLong(key, output); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Iterator<Vec3i> iterator() { | ||||
| 		return new IteratorImpl(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public int size() { | ||||
| 		return impl.size(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public boolean contains(Vec3i pos) { | ||||
| 		return impl.contains(getKey(pos)); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public boolean add(Vec3i pos) { | ||||
| 		return impl.add(getKey(pos)); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public boolean remove(Vec3i pos) { | ||||
| 		return impl.remove(getKey(pos)); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public boolean containsAll(ChunkSet other) { | ||||
| 		if (other instanceof LongBasedChunkSet) { | ||||
| 			return impl.containsAll(((LongBasedChunkSet) other).impl); | ||||
| 		} | ||||
| 		 | ||||
| 		return ChunkSet.super.containsAll((Iterable<? extends Vec3i>) other); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public boolean containsAny(ChunkSet other) { | ||||
| 		return ChunkSet.super.containsAny((Iterable<? extends Vec3i>) other); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void addAll(ChunkSet other) { | ||||
| 		if (other instanceof LongBasedChunkSet) { | ||||
| 			impl.addAll(((LongBasedChunkSet) other).impl); | ||||
| 			return; | ||||
| 		} | ||||
| 		 | ||||
| 		ChunkSet.super.addAll((Iterable<? extends Vec3i>) other); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void removeAll(ChunkSet other) { | ||||
| 		if (other instanceof LongBasedChunkSet) { | ||||
| 			impl.removeAll(((LongBasedChunkSet) other).impl); | ||||
| 			return; | ||||
| 		} | ||||
| 		 | ||||
| 		ChunkSet.super.removeAll((Iterable<? extends Vec3i>) other); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void retainAll(ChunkSet other) { | ||||
| 		if (other instanceof LongBasedChunkSet) { | ||||
| 			impl.retainAll(((LongBasedChunkSet) other).impl); | ||||
| 			return; | ||||
| 		} | ||||
| 		 | ||||
| 		ChunkSet.super.retainAll((Iterable<? extends Vec3i>) other); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void clear() { | ||||
| 		impl.clear(); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void forEach(Consumer<? super Vec3i> action) { | ||||
| 		Vec3i v = Vectors.grab3i(); | ||||
| 		 | ||||
| 		impl.forEach(key -> { | ||||
| 			getVector(key, v); | ||||
| 			action.accept(v); | ||||
| 			return true; | ||||
| 		}); | ||||
| 		 | ||||
| 		Vectors.release(v); | ||||
| 	} | ||||
| 	 | ||||
| 	private class IteratorImpl implements Iterator<Vec3i> { | ||||
| 		 | ||||
| 		private final Vec3i vector = new Vec3i(); | ||||
| 		private final TLongIterator parent = LongBasedChunkSet.this.impl.iterator(); | ||||
|  | ||||
| 		@Override | ||||
| 		public boolean hasNext() { | ||||
| 			return parent.hasNext(); | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public Vec3i next() { | ||||
| 			return getVector(parent.next(), vector); | ||||
| 		} | ||||
| 		 | ||||
| 		@Override | ||||
| 		public void remove() { | ||||
| 			parent.remove(); | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,61 @@ | ||||
| package ru.windcorp.progressia.common.world.tile; | ||||
|  | ||||
| import java.io.DataInput; | ||||
| import java.io.DataOutput; | ||||
| import java.io.IOException; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.world.Coordinates; | ||||
| import ru.windcorp.progressia.common.world.DecodingException; | ||||
| import ru.windcorp.progressia.common.world.PacketChunkChange; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
| import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
|  | ||||
| public class PacketAddTile extends PacketChunkChange { | ||||
| 	 | ||||
| 	private String id; | ||||
| 	private final Vec3i blockInWorld = new Vec3i(); | ||||
| 	private BlockFace face; | ||||
| 	 | ||||
| 	public PacketAddTile() { | ||||
| 		this("Core:AddTile"); | ||||
| 	} | ||||
| 	 | ||||
| 	protected PacketAddTile(String id) { | ||||
| 		super(id); | ||||
| 	} | ||||
| 	 | ||||
| 	public void set(TileData tile, Vec3i blockInWorld, BlockFace face) { | ||||
| 		this.id = tile.getId(); | ||||
| 		this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z); | ||||
| 		this.face = face; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void read(DataInput input) throws IOException, DecodingException { | ||||
| 		this.id = input.readUTF(); | ||||
| 		this.blockInWorld.set(input.readInt(), input.readInt(), input.readInt()); | ||||
| 		this.face = BlockFace.getFaces().get(input.readByte()); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void write(DataOutput output) throws IOException { | ||||
| 		output.writeUTF(this.id); | ||||
| 		output.writeInt(this.blockInWorld.x); | ||||
| 		output.writeInt(this.blockInWorld.y); | ||||
| 		output.writeInt(this.blockInWorld.z); | ||||
| 		output.writeByte(this.face.getId()); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void apply(WorldData world) { | ||||
| 		TileData tile = TileDataRegistry.getInstance().get(id); | ||||
| 		world.getTiles(blockInWorld, face).add(tile); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void getAffectedChunk(Vec3i output) { | ||||
| 		Coordinates.convertInWorldToChunk(this.blockInWorld, output); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,61 @@ | ||||
| package ru.windcorp.progressia.common.world.tile; | ||||
|  | ||||
| import java.io.DataInput; | ||||
| import java.io.DataOutput; | ||||
| import java.io.IOException; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.world.Coordinates; | ||||
| import ru.windcorp.progressia.common.world.DecodingException; | ||||
| import ru.windcorp.progressia.common.world.PacketChunkChange; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
| import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
|  | ||||
| public class PacketRemoveTile extends PacketChunkChange { | ||||
| 	 | ||||
| 	private final Vec3i blockInWorld = new Vec3i(); | ||||
| 	private BlockFace face; | ||||
| 	private int tag; | ||||
| 	 | ||||
| 	public PacketRemoveTile() { | ||||
| 		this("Core:RemoveTile"); | ||||
| 	} | ||||
| 	 | ||||
| 	protected PacketRemoveTile(String id) { | ||||
| 		super(id); | ||||
| 	} | ||||
| 	 | ||||
| 	public void set(Vec3i blockInWorld, BlockFace face, int tag) { | ||||
| 		this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z); | ||||
| 		this.face = face; | ||||
| 		this.tag = tag; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void read(DataInput input) throws IOException, DecodingException { | ||||
| 		this.blockInWorld.set(input.readInt(), input.readInt(), input.readInt()); | ||||
| 		this.face = BlockFace.getFaces().get(input.readByte()); | ||||
| 		this.tag = input.readInt(); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void write(DataOutput output) throws IOException { | ||||
| 		output.writeInt(this.blockInWorld.x); | ||||
| 		output.writeInt(this.blockInWorld.y); | ||||
| 		output.writeInt(this.blockInWorld.z); | ||||
| 		output.writeByte(this.face.getId()); | ||||
| 		output.writeInt(this.tag); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void apply(WorldData world) { | ||||
| 		TileDataStack stack = world.getTiles(blockInWorld, face); | ||||
| 		stack.remove(stack.getIndexByTag(tag)); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void getAffectedChunk(Vec3i output) { | ||||
| 		Coordinates.convertInWorldToChunk(this.blockInWorld, output); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -18,8 +18,9 @@ | ||||
| package ru.windcorp.progressia.common.world.tile; | ||||
|  | ||||
| import ru.windcorp.progressia.common.util.namespaces.Namespaced; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericTile; | ||||
|  | ||||
| public class TileData extends Namespaced { | ||||
| public class TileData extends Namespaced implements GenericTile { | ||||
|  | ||||
| 	public TileData(String id) { | ||||
| 		super(id); | ||||
|   | ||||
| @@ -1,10 +1,15 @@ | ||||
| package ru.windcorp.progressia.common.world.tile; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.world.ChunkData; | ||||
| import ru.windcorp.progressia.common.world.block.BlockData; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericTileStack; | ||||
|  | ||||
| public abstract class TileDataStack extends GenericTileStack<TileData, ChunkData> { | ||||
| public abstract class TileDataStack | ||||
| extends GenericTileStack< | ||||
| 	TileDataStack, | ||||
| 	TileData, | ||||
| 	ChunkData | ||||
| > { | ||||
| 	 | ||||
| 	/** | ||||
| 	 * Inserts the specified tile at the specified position in this stack. | ||||
| @@ -21,6 +26,16 @@ public abstract class TileDataStack extends GenericTileStack<TileData, ChunkData | ||||
| 	@Override | ||||
| 	public abstract void add(int index, TileData tile); | ||||
| 	 | ||||
| 	/** | ||||
| 	 * Adds the specified tile at the end of this stack assigning it the provided tag. | ||||
| 	 * This method is useful for copying stacks when preserving tags is necessary. | ||||
| 	 * @param tile the tile to add | ||||
| 	 * @param tag the tag to assign the new tile | ||||
| 	 * @throws IllegalArgumentException if this stack already contains a tile with the | ||||
| 	 * provided tag | ||||
| 	 */ | ||||
| 	public abstract void load(TileData tile, int tag); | ||||
|  | ||||
| 	/** | ||||
| 	 * Replaces the tile at the specified position in this stack with the specified tile. | ||||
| 	 * @param index index of the tile to replace | ||||
| @@ -54,15 +69,6 @@ public abstract class TileDataStack extends GenericTileStack<TileData, ChunkData | ||||
| 	 | ||||
| 	public abstract int getTagByIndex(int index); | ||||
| 	 | ||||
| 	/* | ||||
| 	 * Implementation | ||||
| 	 */ | ||||
| 	 | ||||
| 	@Override | ||||
| 	public Vec3i getChunkPos() { | ||||
| 		return getChunk().getPosition(); | ||||
| 	} | ||||
| 	 | ||||
| 	/* | ||||
| 	 * Aliases and overloads | ||||
| 	 */ | ||||
|   | ||||
| @@ -1,92 +0,0 @@ | ||||
| package ru.windcorp.progressia.server; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import gnu.trove.set.TLongSet; | ||||
| import gnu.trove.set.hash.TLongHashSet; | ||||
| import ru.windcorp.progressia.common.util.CoordinatePacker; | ||||
| import ru.windcorp.progressia.common.world.ChunkData; | ||||
| import ru.windcorp.progressia.test.TestContent; | ||||
|  | ||||
| public class ChunkLoadManager { | ||||
| 	 | ||||
| 	private final Server server; | ||||
| 	 | ||||
| 	private final Collection<Collection<? extends ChunkLoader>> allChunkLoaders = | ||||
| 			Collections.synchronizedCollection(new ArrayList<>()); | ||||
| 	 | ||||
| 	private final TLongSet requested = new TLongHashSet(); | ||||
| 	private final TLongSet toLoad = new TLongHashSet(); | ||||
| 	private final TLongSet toUnload = new TLongHashSet(); | ||||
|  | ||||
| 	public ChunkLoadManager(Server server) { | ||||
| 		this.server = server; | ||||
| 		allChunkLoaders.add(server.getPlayerManager().getPlayers()); | ||||
| 	} | ||||
| 	 | ||||
| 	public void tick() { | ||||
| 		gatherRequests(); | ||||
| 		updateQueues(); | ||||
| 		processQueues(); | ||||
| 	} | ||||
|  | ||||
| 	private void gatherRequests() { | ||||
| 		requested.clear(); | ||||
| 		 | ||||
| 		allChunkLoaders.forEach(collection -> { | ||||
| 			collection.forEach(this::gatherRequests); | ||||
| 		}); | ||||
| 	} | ||||
| 	 | ||||
| 	private void gatherRequests(ChunkLoader loader) { | ||||
| 		loader.requestChunksToLoad(v -> requested.add(CoordinatePacker.pack3IntsIntoLong(v))); | ||||
| 	} | ||||
| 	 | ||||
| 	private void updateQueues() { | ||||
| 		TLongSet loaded = getServer().getWorld().getData().getChunkKeys(); | ||||
| 		 | ||||
| 		toLoad.clear(); | ||||
| 		toLoad.addAll(requested); | ||||
| 		toLoad.removeAll(loaded); | ||||
| 		 | ||||
| 		toUnload.clear(); | ||||
| 		toUnload.addAll(loaded); | ||||
| 		toUnload.removeAll(requested); | ||||
| 	} | ||||
|  | ||||
| 	private void processQueues() { | ||||
| 		Vec3i v = new Vec3i(); | ||||
| 		 | ||||
| 		toLoad.forEach(key -> { | ||||
| 			loadChunk(CoordinatePacker.unpack3IntsFromLong(key, v)); | ||||
| 			return true; | ||||
| 		}); | ||||
| 		 | ||||
| 		toUnload.forEach(key -> { | ||||
| 			unloadChunk(CoordinatePacker.unpack3IntsFromLong(key, v)); | ||||
| 			return true; | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	public Server getServer() { | ||||
| 		return server; | ||||
| 	} | ||||
| 	 | ||||
| 	public void loadChunk(Vec3i pos) { | ||||
| 		 | ||||
| 		ChunkData chunk = new ChunkData(pos, getServer().getWorld().getData()); | ||||
| 		TestContent.generateChunk(chunk); | ||||
| 		getServer().getWorld().getData().addChunk(chunk); | ||||
| 		 | ||||
| 	} | ||||
| 	 | ||||
| 	public void unloadChunk(Vec3i pos) { | ||||
| 		 | ||||
| 		getServer().getWorld().getData().removeChunk(getServer().getWorld().getData().getChunk(pos)); | ||||
| 		 | ||||
| 	} | ||||
|  | ||||
| } | ||||
							
								
								
									
										204
									
								
								src/main/java/ru/windcorp/progressia/server/ChunkManager.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								src/main/java/ru/windcorp/progressia/server/ChunkManager.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,204 @@ | ||||
| package ru.windcorp.progressia.server; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.Map; | ||||
| import java.util.WeakHashMap; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.world.ChunkData; | ||||
| import ru.windcorp.progressia.common.world.PacketRevokeChunk; | ||||
| import ru.windcorp.progressia.common.world.PacketSendChunk; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
| import ru.windcorp.progressia.common.world.generic.ChunkSet; | ||||
| import ru.windcorp.progressia.common.world.generic.ChunkSets; | ||||
| import ru.windcorp.progressia.test.TestContent; | ||||
| import ru.windcorp.progressia.test.TestWorldDiskIO; | ||||
|  | ||||
| public class ChunkManager { | ||||
| 	 | ||||
| 	private class PlayerVision { | ||||
|  | ||||
| 		private final ChunkSet visible = ChunkSets.newSyncHashSet(); | ||||
| 		private final ChunkSet requested = ChunkSets.newHashSet(); | ||||
| 		private final ChunkSet toSend = ChunkSets.newHashSet(); | ||||
| 		private final ChunkSet toRevoke = ChunkSets.newHashSet(); | ||||
| 		 | ||||
| 		public boolean isChunkVisible(Vec3i chunkPos) { | ||||
| 			return visible.contains(chunkPos); | ||||
| 		} | ||||
| 		 | ||||
| 		public void gatherRequests(Player player) { | ||||
| 			requested.clear(); | ||||
| 			player.requestChunksToLoad(requested::add); | ||||
| 		} | ||||
| 		 | ||||
| 		public void updateQueues(Player player) { | ||||
| 			toSend.clear(); | ||||
| 			toSend.addAll(requested); | ||||
| 			toSend.removeAll(visible); | ||||
| 			toSend.retainAll(loaded); | ||||
| 			 | ||||
| 			toRevoke.clear(); | ||||
| 			toRevoke.addAll(visible); | ||||
| 			toRevoke.removeIf(v -> loaded.contains(v) && requested.contains(v)); | ||||
| 		} | ||||
| 		 | ||||
| 		public void processQueues(Player player) { | ||||
| 			toRevoke.forEach(chunkPos -> revokeChunk(player, chunkPos)); | ||||
| 			toRevoke.clear(); | ||||
| 			 | ||||
| 			toSend.forEach(chunkPos -> sendChunk(player, chunkPos)); | ||||
| 			toSend.clear(); | ||||
| 		} | ||||
| 		 | ||||
| 	} | ||||
|  | ||||
| 	private final Server server; | ||||
| 	 | ||||
| 	private final ChunkSet loaded; | ||||
| 	private final ChunkSet requested = ChunkSets.newHashSet(); | ||||
| 	private final ChunkSet toLoad = ChunkSets.newHashSet(); | ||||
| 	private final ChunkSet toUnload = ChunkSets.newHashSet(); | ||||
| 	 | ||||
| 	// TODO replace with a normal Map managed by some sort of PlayerListener, weak maps are weak | ||||
| 	private final Map<Player, PlayerVision> visions = Collections.synchronizedMap(new WeakHashMap<>()); | ||||
|  | ||||
| 	public ChunkManager(Server server) { | ||||
| 		this.server = server; | ||||
| 		this.loaded = server.getWorld().getData().getLoadedChunks(); | ||||
| 	} | ||||
|  | ||||
| 	public void tick() { | ||||
| 		synchronized (getServer().getWorld().getData()) { | ||||
| 			synchronized (visions) { | ||||
| 				gatherRequests(); | ||||
| 				updateQueues(); | ||||
| 				processQueues(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private void gatherRequests() { | ||||
| 		requested.clear(); | ||||
| 		 | ||||
| 		server.getPlayerManager().getPlayers().forEach(p -> { | ||||
| 			PlayerVision vision = getVision(p, true); | ||||
| 			vision.gatherRequests(p); | ||||
| 			requested.addAll(vision.requested); | ||||
| 		}); | ||||
| 	} | ||||
| 	 | ||||
| 	private void updateQueues() { | ||||
| 		toLoad.clear(); | ||||
| 		toLoad.addAll(requested); | ||||
| 		toLoad.removeAll(loaded); | ||||
| 		 | ||||
| 		toUnload.clear(); | ||||
| 		toUnload.addAll(loaded); | ||||
| 		toUnload.removeAll(requested); | ||||
| 		 | ||||
| 		visions.forEach((p, v) -> { | ||||
| 			v.updateQueues(p); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	private void processQueues() { | ||||
| 		toUnload.forEach(this::unloadChunk); | ||||
| 		toUnload.clear(); | ||||
| 		toLoad.forEach(this::loadChunk); | ||||
| 		toLoad.clear(); | ||||
| 		 | ||||
| 		visions.forEach((p, v) -> { | ||||
| 			v.processQueues(p); | ||||
| 		}); | ||||
| 	} | ||||
| 	 | ||||
| 	private PlayerVision getVision(Player player, boolean createIfMissing) { | ||||
| 		return createIfMissing ? visions.computeIfAbsent(player, k -> new PlayerVision()) : visions.get(player); | ||||
| 	} | ||||
| 	 | ||||
| 	public void loadChunk(Vec3i chunkPos) { | ||||
|  | ||||
| 		WorldData world = getServer().getWorld().getData(); | ||||
| 		 | ||||
| 		ChunkData chunk = TestWorldDiskIO.tryToLoad(chunkPos, world); | ||||
| 		if (chunk == null) { | ||||
| 			chunk = new ChunkData(chunkPos, world); | ||||
| 			TestContent.generateChunk(chunk); | ||||
| 		} | ||||
| 		 | ||||
| 		world.addChunk(chunk); | ||||
| 		 | ||||
| 	} | ||||
| 	 | ||||
| 	public void unloadChunk(Vec3i chunkPos) { | ||||
| 		 | ||||
| 		WorldData world = getServer().getWorld().getData(); | ||||
| 		 | ||||
| 		ChunkData chunk = world.getChunk(chunkPos); | ||||
| 		if (chunk == null) { | ||||
| 			throw new IllegalStateException(String.format( | ||||
| 					"Chunk (%d; %d; %d) not loaded, cannot unload", | ||||
| 					chunkPos.x, chunkPos.y, chunkPos.z | ||||
| 			)); | ||||
| 		} | ||||
| 		 | ||||
| 		world.removeChunk(chunk); | ||||
| 		 | ||||
| 		TestWorldDiskIO.saveChunk(chunk); | ||||
| 		 | ||||
| 	} | ||||
|  | ||||
| 	public void sendChunk(Player player, Vec3i chunkPos) { | ||||
| 		ChunkData chunk = server.getWorld().getData().getChunk(chunkPos); | ||||
| 		 | ||||
| 		if (chunk == null) { | ||||
| 			throw new IllegalStateException(String.format( | ||||
| 					"Chunk (%d; %d; %d) is not loaded, cannot send", | ||||
| 					chunkPos.x, chunkPos.y, chunkPos.z | ||||
| 			)); | ||||
| 		} | ||||
| 		 | ||||
| 		PacketSendChunk packet = new PacketSendChunk(); | ||||
| 		packet.set(chunk);		 | ||||
| 		player.getClient().sendPacket(packet); | ||||
| 		 | ||||
| 		getVision(player, true).visible.add(chunkPos); | ||||
| 	} | ||||
| 	 | ||||
| 	public void revokeChunk(Player player, Vec3i chunkPos) { | ||||
| 		PacketRevokeChunk packet = new PacketRevokeChunk(); | ||||
| 		packet.set(chunkPos); | ||||
| 		player.getClient().sendPacket(packet); | ||||
| 		 | ||||
| 		PlayerVision vision = getVision(player, false); | ||||
| 		if (vision != null) { | ||||
| 			vision.visible.remove(chunkPos); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	public boolean isChunkVisible(Vec3i chunkPos, Player player) { | ||||
| 		PlayerVision vision = getVision(player, false); | ||||
| 		 | ||||
| 		if (vision == null) { | ||||
| 			return false; | ||||
| 		} | ||||
| 		 | ||||
| 		return vision.isChunkVisible(chunkPos); | ||||
| 	} | ||||
| 	 | ||||
| 	public ChunkSet getVisibleChunks(Player player) { | ||||
| 		PlayerVision vision = getVision(player, false); | ||||
| 		 | ||||
| 		if (vision == null) { | ||||
| 			return ChunkSets.empty(); | ||||
| 		} | ||||
| 		 | ||||
| 		return vision.visible; | ||||
| 	} | ||||
|  | ||||
| 	public Server getServer() { | ||||
| 		return server; | ||||
| 	} | ||||
|  | ||||
| } | ||||
							
								
								
									
										177
									
								
								src/main/java/ru/windcorp/progressia/server/EntityManager.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								src/main/java/ru/windcorp/progressia/server/EntityManager.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | ||||
| package ru.windcorp.progressia.server; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.Map; | ||||
| import java.util.WeakHashMap; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import gnu.trove.TCollections; | ||||
| import gnu.trove.iterator.TLongIterator; | ||||
| import gnu.trove.set.TLongSet; | ||||
| import gnu.trove.set.hash.TLongHashSet; | ||||
| import ru.windcorp.jputil.chars.StringUtil; | ||||
| import ru.windcorp.progressia.common.util.Vectors; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
| import ru.windcorp.progressia.common.world.entity.PacketRevokeEntity; | ||||
| import ru.windcorp.progressia.common.world.generic.ChunkSet; | ||||
|  | ||||
| public class EntityManager { | ||||
| 	 | ||||
| 	private class PlayerVision { | ||||
|  | ||||
| 		private final TLongSet visible = TCollections.synchronizedSet(new TLongHashSet()); | ||||
| 		private final TLongSet requested = new TLongHashSet(); | ||||
| 		private final TLongSet toSend = new TLongHashSet(); | ||||
| 		private final TLongSet toRevoke = new TLongHashSet(); | ||||
| 		 | ||||
| 		public boolean isEntityVisible(long entityId) { | ||||
| 			return visible.contains(entityId); | ||||
| 		} | ||||
| 		 | ||||
| 		public void gatherRequests(Player player) { | ||||
| 			requested.clear(); | ||||
| 			 | ||||
| 			ChunkSet visibleChunks = player.getClient().getVisibleChunks(); | ||||
| 			Vec3i v = Vectors.grab3i(); | ||||
| 			 | ||||
| 			getServer().getWorld().forEachEntity(entity -> { | ||||
| 				if (visibleChunks.contains(entity.getChunkCoords(v))) { | ||||
| 					requested.add(entity.getEntityId()); | ||||
| 				} | ||||
| 			}); | ||||
| 			 | ||||
| 			Vectors.release(v); | ||||
| 		} | ||||
| 		 | ||||
| 		public void updateQueues(Player player) { | ||||
| 			toSend.clear(); | ||||
| 			toSend.addAll(requested); | ||||
| 			toSend.removeAll(visible); | ||||
| 			toSend.retainAll(loaded); | ||||
| 			 | ||||
| 			toRevoke.clear(); | ||||
| 			 | ||||
| 			for (TLongIterator it = visible.iterator(); it.hasNext();) { | ||||
| 				long entityId = it.next(); | ||||
| 				if (!loaded.contains(entityId) || !requested.contains(entityId)) { | ||||
| 					toRevoke.add(entityId); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		 | ||||
| 		public void processQueues(Player player) { | ||||
| 			toRevoke.forEach(entityId -> { | ||||
| 				revokeEntity(player, entityId); | ||||
| 				return true; | ||||
| 			}); | ||||
| 			toRevoke.clear(); | ||||
| 			 | ||||
| 			toSend.forEach(entityId -> { | ||||
| 				sendEntity(player, entityId); | ||||
| 				return true; | ||||
| 			}); | ||||
| 			toSend.clear(); | ||||
| 		} | ||||
| 		 | ||||
| 	} | ||||
|  | ||||
| 	private final Server server; | ||||
| 	 | ||||
| 	private final TLongSet loaded; | ||||
| 	 | ||||
| 	// TODO replace with a normal Map managed by some sort of PlayerListener, weak maps are weak | ||||
| 	private final Map<Player, PlayerVision> visions = Collections.synchronizedMap(new WeakHashMap<>()); | ||||
|  | ||||
| 	public EntityManager(Server server) { | ||||
| 		this.server = server; | ||||
| 		this.loaded = server.getWorld().getData().getLoadedEntities(); | ||||
| 	} | ||||
|  | ||||
| 	public void tick() { | ||||
| 		synchronized (getServer().getWorld().getData()) { | ||||
| 			synchronized (visions) { | ||||
| 				gatherRequests(); | ||||
| 				updateQueues(); | ||||
| 				processQueues(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private void gatherRequests() { | ||||
| 		server.getPlayerManager().getPlayers().forEach(p -> { | ||||
| 			PlayerVision vision = getVision(p, true); | ||||
| 			vision.gatherRequests(p); | ||||
| 		}); | ||||
| 	} | ||||
| 	 | ||||
| 	private void updateQueues() { | ||||
| 		visions.forEach((p, v) -> { | ||||
| 			v.updateQueues(p); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	private void processQueues() { | ||||
| 		visions.forEach((p, v) -> { | ||||
| 			v.processQueues(p); | ||||
| 		}); | ||||
| 	} | ||||
| 	 | ||||
| 	private PlayerVision getVision(Player player, boolean createIfMissing) { | ||||
| 		return createIfMissing ? visions.computeIfAbsent(player, k -> new PlayerVision()) : visions.get(player); | ||||
| 	} | ||||
| 	 | ||||
| 	public void sendEntity(Player player, long entityId) { | ||||
| 		 | ||||
| 		EntityData entity = server.getWorld().getData().getEntity(entityId); | ||||
| 		 | ||||
| 		if (entity == null) { | ||||
| 			throw new IllegalStateException( | ||||
| 					"Entity with entity ID " + new String(StringUtil.toFullHex(entityId)) + " is not loaded, cannot send" | ||||
| 			); | ||||
| 		} | ||||
| 		 | ||||
| 		PacketSendEntity packet = new PacketSendEntity(); | ||||
| 		packet.set(entity); | ||||
| 		player.getClient().sendPacket(packet); | ||||
| 		 | ||||
| 		getVision(player, true).visible.add(entityId); | ||||
| 	} | ||||
| 	 | ||||
| 	public void revokeEntity(Player player, long entityId) { | ||||
| 		PacketRevokeEntity packet = new PacketRevokeEntity(); | ||||
| 		packet.set(entityId); | ||||
| 		player.getClient().sendPacket(packet); | ||||
| 		 | ||||
| 		PlayerVision vision = getVision(player, false); | ||||
| 		if (vision != null) { | ||||
| 			vision.visible.remove(entityId); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	public boolean isEntityVisible(long entityId, Player player) { | ||||
| 		PlayerVision vision = getVision(player, false); | ||||
| 		 | ||||
| 		if (vision == null) { | ||||
| 			return false; | ||||
| 		} | ||||
| 		 | ||||
| 		return vision.isEntityVisible(entityId); | ||||
| 	} | ||||
| 	 | ||||
| 	private static final TLongSet EMPTY_LONG_SET = TCollections.unmodifiableSet(new TLongHashSet()); | ||||
| 	 | ||||
| 	public TLongSet getVisibleEntities(Player player) { | ||||
| 		PlayerVision vision = getVision(player, false); | ||||
| 		 | ||||
| 		if (vision == null) { | ||||
| 			return EMPTY_LONG_SET; | ||||
| 		} | ||||
| 		 | ||||
| 		return vision.visible; | ||||
| 	} | ||||
| 	 | ||||
| 	public Server getServer() { | ||||
| 		return server; | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,70 @@ | ||||
| package ru.windcorp.progressia.server; | ||||
|  | ||||
| import java.io.DataInput; | ||||
| import java.io.DataOutput; | ||||
| import java.io.IOException; | ||||
|  | ||||
| import ru.windcorp.progressia.common.state.IOContext; | ||||
| import ru.windcorp.progressia.common.util.DataBuffer; | ||||
| import ru.windcorp.progressia.common.util.crash.CrashReports; | ||||
| import ru.windcorp.progressia.common.world.DecodingException; | ||||
| import ru.windcorp.progressia.common.world.PacketWorldChange; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityDataRegistry; | ||||
|  | ||||
| public class PacketSendEntity extends PacketWorldChange { | ||||
| 	 | ||||
| 	private String id; | ||||
| 	private long entityId; | ||||
| 	private final DataBuffer buffer = new DataBuffer(); | ||||
| 	 | ||||
| 	public PacketSendEntity() { | ||||
| 		this("Core:SendEntity"); | ||||
| 	} | ||||
| 	 | ||||
| 	protected PacketSendEntity(String id) { | ||||
| 		super(id); | ||||
| 	} | ||||
| 	 | ||||
| 	public void set(EntityData entity) { | ||||
| 		this.id = entity.getId(); | ||||
| 		this.entityId = entity.getEntityId(); | ||||
| 		 | ||||
| 		try { | ||||
| 			entity.write(this.buffer.getWriter(), IOContext.COMMS); | ||||
| 		} catch (IOException e) { | ||||
| 			CrashReports.report(e, "Could not write an entity into an internal buffer"); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void read(DataInput input) throws IOException, DecodingException { | ||||
| 		this.id = input.readUTF(); | ||||
| 		this.entityId = input.readLong(); | ||||
| 		this.buffer.fill(input, input.readInt()); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void write(DataOutput output) throws IOException { | ||||
| 		output.writeUTF(this.id); | ||||
| 		output.writeLong(this.entityId); | ||||
| 		output.writeInt(this.buffer.getSize()); | ||||
| 		this.buffer.flush(output); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void apply(WorldData world) { | ||||
| 		EntityData entity = EntityDataRegistry.getInstance().create(this.id); | ||||
| 		 | ||||
| 		entity.setEntityId(this.entityId); | ||||
| 		try { | ||||
| 			entity.read(this.buffer.getReader(), IOContext.COMMS); | ||||
| 		} catch (IOException e) { | ||||
| 			CrashReports.report(e, "Could not read an entity from an internal buffer"); | ||||
| 		} | ||||
| 		 | ||||
| 		world.addEntity(entity); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -18,6 +18,8 @@ public class Player extends PlayerData implements ChunkLoader { | ||||
| 		super(entity); | ||||
| 		this.server = server; | ||||
| 		this.client = client; | ||||
| 		 | ||||
| 		client.setPlayer(this); | ||||
| 	} | ||||
| 	 | ||||
| 	public Server getServer() { | ||||
| @@ -34,22 +36,28 @@ public class Player extends PlayerData implements ChunkLoader { | ||||
| 		Coordinates.convertInWorldToChunk(start, start); | ||||
| 		 | ||||
| 		Vec3i cursor = new Vec3i(); | ||||
| 		float radius = getServer().getLoadDistance(this); | ||||
| 		float radiusSq = radius / Units.get(16.0f, "m"); | ||||
| 		radiusSq *= radiusSq; | ||||
| 		float radius = getServer().getLoadDistance(this) / Units.get(16.0f, "m"); | ||||
| 		 | ||||
| 		float radiusSq = radius * radius; | ||||
| 		int iRadius = (int) Math.ceil(radius); | ||||
| 		 | ||||
| 		for (cursor.x = -iRadius; cursor.x <= +iRadius; ++cursor.x) { | ||||
| 			for (cursor.y = -iRadius; cursor.y <= +iRadius; ++cursor.y) { | ||||
| 				for (cursor.z = -iRadius; cursor.z <= +iRadius; ++cursor.z) { | ||||
| 					if (cursor.x * cursor.x + cursor.y * cursor.y + cursor.z * cursor.z <= radius) { | ||||
| 					if (cursor.x * cursor.x + cursor.y * cursor.y + cursor.z * cursor.z <= radiusSq) { | ||||
| 						 | ||||
| 						cursor.add(start); | ||||
| 						chunkConsumer.accept(cursor); | ||||
| 						cursor.sub(start); | ||||
| 						 | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	public String getLogin() { | ||||
| 		return getClient().getLogin(); | ||||
| 	} | ||||
| 	 | ||||
| } | ||||
|   | ||||
| @@ -4,8 +4,13 @@ import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
|  | ||||
| import glm.vec._2.Vec2; | ||||
| import glm.vec._3.Vec3; | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.util.Vectors; | ||||
| import ru.windcorp.progressia.common.util.crash.CrashReports; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityDataRegistry; | ||||
| import ru.windcorp.progressia.test.TestContent; | ||||
|  | ||||
| public class PlayerManager { | ||||
| @@ -29,14 +34,35 @@ public class PlayerManager { | ||||
| 	public EntityData conjurePlayerEntity(String login) { | ||||
| 		// TODO Live up to the name | ||||
| 		if (TestContent.PLAYER_LOGIN.equals(login)) { | ||||
| 			// TODO load appropriate chunks | ||||
| 			return getServer().getWorld().getData().getEntity(TestContent.PLAYER_ENTITY_ID); | ||||
| 			 | ||||
| 			Vec3i chunkPos = Vectors.ZERO_3i; | ||||
| 			 | ||||
| 			if (getServer().getWorld().getChunk(chunkPos) == null) { | ||||
| 				getServer().getChunkManager().loadChunk(chunkPos); | ||||
| 			} | ||||
| 			 | ||||
| 			EntityData entity = spawnPlayerEntity(login); | ||||
| 			return entity; | ||||
| 		} else { | ||||
| 			CrashReports.report(null, "Unknown login %s, javahorse stupid", login); | ||||
| 			return null; | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	private EntityData spawnPlayerEntity(String login) { | ||||
| 		EntityData player = EntityDataRegistry.getInstance().create("Test:Player"); | ||||
| 		 | ||||
| 		player.setEntityId(TestContent.PLAYER_ENTITY_ID); | ||||
| 		player.setPosition(new Vec3(8, 8, 8)); | ||||
| 		player.setDirection(new Vec2( | ||||
| 				Math.toRadians(40), Math.toRadians(10) | ||||
| 		)); | ||||
| 		 | ||||
| 		getServer().getWorld().getData().addEntity(player); | ||||
| 		 | ||||
| 		return player; | ||||
| 	} | ||||
|  | ||||
| 	public Server getServer() { | ||||
| 		return server; | ||||
| 	} | ||||
|   | ||||
| @@ -29,8 +29,10 @@ public class Server { | ||||
| 	 | ||||
| 	private final ServerThread serverThread; | ||||
| 	 | ||||
| 	private final ClientManager clientManager = new ClientManager(this); | ||||
| 	private final PlayerManager playerManager = new PlayerManager(this); | ||||
| 	private final ClientManager clientManager; | ||||
| 	private final PlayerManager playerManager; | ||||
| 	private final  ChunkManager  chunkManager; | ||||
| 	private final EntityManager entityManager; | ||||
| 	 | ||||
| 	private final TaskQueue taskQueue = new TaskQueue(this::isServerThread); | ||||
| 	 | ||||
| @@ -40,7 +42,14 @@ public class Server { | ||||
| 		this.world = new WorldLogic(world, this); | ||||
| 		this.serverThread = new ServerThread(this); | ||||
|  | ||||
| 		schedule(this::scheduleChunkTicks); | ||||
| 		this.clientManager = new ClientManager(this); | ||||
| 		this.playerManager = new PlayerManager(this); | ||||
| 		this.chunkManager = new ChunkManager(this); | ||||
| 		this.entityManager = new EntityManager(this); | ||||
| 		 | ||||
| 		schedule(this::scheduleWorldTicks); | ||||
| 		schedule(chunkManager::tick); | ||||
| 		schedule(entityManager::tick); | ||||
| 	} | ||||
| 	 | ||||
| 	/** | ||||
| @@ -64,6 +73,10 @@ public class Server { | ||||
| 		return playerManager; | ||||
| 	} | ||||
| 	 | ||||
| 	public ChunkManager getChunkManager() { | ||||
| 		return chunkManager; | ||||
| 	} | ||||
| 	 | ||||
| 	/** | ||||
| 	 * Checks if this thread is the main thread of this server. | ||||
| 	 * @return {@code true} iff the invocation occurs in server main thread | ||||
| @@ -187,8 +200,9 @@ public class Server { | ||||
| 		serverThread.stop(); | ||||
| 	} | ||||
| 	 | ||||
| 	private void scheduleChunkTicks(Server server) { | ||||
| 	private void scheduleWorldTicks(Server server) { | ||||
| 		server.getWorld().getChunks().forEach(chunk -> requestEvaluation(chunk.getTickTask())); | ||||
| 		requestEvaluation(server.getWorld().getTickEntitiesTask()); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
|   | ||||
| @@ -16,7 +16,6 @@ public class ServerState { | ||||
| 	 | ||||
| 	public static void startServer() { | ||||
| 		Server server = new Server(new WorldData()); | ||||
| 		server.getWorld().getData().tmp_generate(); | ||||
| 		setInstance(server); | ||||
| 		server.start(); | ||||
| 	} | ||||
|   | ||||
| @@ -1,20 +1,18 @@ | ||||
| package ru.windcorp.progressia.server.comms; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.concurrent.atomic.AtomicInteger; | ||||
|  | ||||
| import org.apache.logging.log4j.LogManager; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import gnu.trove.TCollections; | ||||
| import gnu.trove.map.TIntObjectMap; | ||||
| import gnu.trove.map.hash.TIntObjectHashMap; | ||||
| import ru.windcorp.progressia.common.comms.CommsChannel.State; | ||||
| import ru.windcorp.progressia.common.comms.packets.Packet; | ||||
| import ru.windcorp.progressia.common.comms.packets.PacketLoadChunk; | ||||
| import ru.windcorp.progressia.common.comms.packets.PacketSetLocalPlayer; | ||||
| import ru.windcorp.progressia.common.io.ChunkIO; | ||||
| import ru.windcorp.progressia.common.util.crash.CrashReports; | ||||
| import ru.windcorp.progressia.common.world.ChunkData; | ||||
| import ru.windcorp.progressia.common.world.PacketSetLocalPlayer; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
| import ru.windcorp.progressia.server.Player; | ||||
| import ru.windcorp.progressia.server.Server; | ||||
| @@ -45,10 +43,10 @@ public class ClientManager { | ||||
| 			 | ||||
| 			if (client instanceof ClientChat) { | ||||
| 				addClientChat((ClientChat) client); | ||||
| 			} | ||||
| 				 | ||||
| 			if (client instanceof ClientPlayer) { | ||||
| 				addClientPlayer((ClientPlayer) client); | ||||
| 				if (client instanceof ClientPlayer) { | ||||
| 					addClientPlayer((ClientPlayer) client); | ||||
| 				} | ||||
| 			} | ||||
| 			 | ||||
| 			client.addListener(new DefaultServerCommsListener(this, client)); | ||||
| @@ -61,29 +59,15 @@ public class ClientManager { | ||||
|  | ||||
| 	private void addClientPlayer(ClientPlayer client) { | ||||
| 		String login = client.getLogin(); | ||||
| 		 | ||||
| 		EntityData entity = getServer().getPlayerManager().conjurePlayerEntity(login); | ||||
| 		 | ||||
| 		Player player = new Player(entity, getServer(), client); | ||||
| 		 | ||||
| 		getServer().getPlayerManager().getPlayers().add(player); | ||||
|  | ||||
| 		for (ChunkData chunk : server.getWorld().getData().getChunks()) { | ||||
| 			PacketLoadChunk packet = new PacketLoadChunk("Core:LoadChunk"); | ||||
| 			packet.getPosition().set( | ||||
| 					chunk.getPosition().x, | ||||
| 					chunk.getPosition().y, | ||||
| 					chunk.getPosition().z | ||||
| 			); | ||||
| 			 | ||||
| 			try { | ||||
| 				ChunkIO.save(chunk, packet.getData().getOutputStream()); | ||||
| 			} catch (IOException e) { | ||||
| 				CrashReports.report(e, "ClientManager fjcked up. javahorse stupid"); | ||||
| 			} | ||||
| 			client.sendPacket(packet); | ||||
| 		} | ||||
|  | ||||
| 		client.sendPacket(new PacketSetLocalPlayer(entity.getEntityId())); | ||||
| 		PacketSetLocalPlayer packet = new PacketSetLocalPlayer(); | ||||
| 		LogManager.getLogger().info("Sending local player ID {}", EntityData.formatEntityId(entity.getEntityId())); | ||||
| 		packet.set(entity.getEntityId()); | ||||
| 		client.sendPacket(packet); | ||||
| 	} | ||||
|  | ||||
| 	public void disconnectClient(Client client) { | ||||
| @@ -91,7 +75,11 @@ public class ClientManager { | ||||
| 		clientsById.remove(client.getId()); | ||||
| 	} | ||||
| 	 | ||||
| 	public void broadcastGamePacket(Packet packet) { | ||||
| 	/** | ||||
| 	 * Sends the provided packet to all connected player clients. | ||||
| 	 * @param packet the packet to broadcast | ||||
| 	 */ | ||||
| 	public void broadcastToAllPlayers(Packet packet) { | ||||
| 		getClients().forEach(c -> { | ||||
| 				if (c.getState() != State.CONNECTED) return; | ||||
| 				if (!(c instanceof ClientPlayer)) return; | ||||
| @@ -99,6 +87,34 @@ public class ClientManager { | ||||
| 		}); | ||||
| 	} | ||||
| 	 | ||||
| 	/** | ||||
| 	 * Sends the provided packet to all connected player clients that can see the chunk identified by {@code chunkPos}. | ||||
| 	 * @param packet the packet to broadcast | ||||
| 	 * @param chunkPos the chunk coordinates of the chunk that must be visible | ||||
| 	 */ | ||||
| 	public void broadcastLocal(Packet packet, Vec3i chunkPos) { | ||||
| 		getClients().forEach(c -> { | ||||
| 				if (c.getState() != State.CONNECTED) return; | ||||
| 				if (!(c instanceof ClientPlayer)) return; | ||||
| 				if (!((ClientPlayer) c).isChunkVisible(chunkPos)) return; | ||||
| 				c.sendPacket(packet); | ||||
| 		}); | ||||
| 	} | ||||
| 	 | ||||
| 	/** | ||||
| 	 * Sends the provided packet to all connected player clients that can see the entity identified by {@code entityId}. | ||||
| 	 * @param packet the packet to broadcast | ||||
| 	 * @param entityId the ID of the entity that must be visible | ||||
| 	 */ | ||||
| 	public void broadcastLocal(Packet packet, long entityId) { | ||||
| 		getClients().forEach(c -> { | ||||
| 				if (c.getState() != State.CONNECTED) return; | ||||
| 				if (!(c instanceof ClientPlayer)) return; | ||||
| 				if (!((ClientPlayer) c).isChunkVisible(entityId)) return; | ||||
| 				c.sendPacket(packet); | ||||
| 		}); | ||||
| 	} | ||||
| 	 | ||||
| 	public Collection<Client> getClients() { | ||||
| 		return clients; | ||||
| 	} | ||||
|   | ||||
| @@ -1,11 +1,40 @@ | ||||
| package ru.windcorp.progressia.server.comms; | ||||
|  | ||||
| public abstract class ClientPlayer extends Client { | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.world.generic.ChunkSet; | ||||
| import ru.windcorp.progressia.common.world.generic.ChunkSets; | ||||
| import ru.windcorp.progressia.server.Player; | ||||
|  | ||||
| public abstract class ClientPlayer extends ClientChat { | ||||
| 	 | ||||
| 	private Player player; | ||||
|  | ||||
| 	public ClientPlayer(int id) { | ||||
| 		super(id); | ||||
| 	} | ||||
| 	 | ||||
| 	public Player getPlayer() { | ||||
| 		return player; | ||||
| 	} | ||||
| 	 | ||||
| 	public void setPlayer(Player player) { | ||||
| 		this.player = player; | ||||
| 	} | ||||
| 	 | ||||
| 	public abstract String getLogin(); | ||||
| 	 | ||||
| 	public boolean isChunkVisible(Vec3i chunkPos) { | ||||
| 		if (player == null) return false; | ||||
| 		return player.getServer().getChunkManager().isChunkVisible(chunkPos, player); | ||||
| 	} | ||||
| 	 | ||||
| 	public ChunkSet getVisibleChunks() { | ||||
| 		if (player == null) return ChunkSets.empty(); | ||||
| 		return player.getServer().getChunkManager().getVisibleChunks(player); | ||||
| 	} | ||||
| 	 | ||||
| 	public boolean isChunkVisible(long entityId) { | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -11,14 +11,12 @@ import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.world.ChunkData; | ||||
| import ru.windcorp.progressia.common.world.Coordinates; | ||||
| import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericChunk; | ||||
| import ru.windcorp.progressia.common.world.tile.TileDataStack; | ||||
| import ru.windcorp.progressia.common.world.tile.TileReference; | ||||
| import ru.windcorp.progressia.server.world.block.BlockLogic; | ||||
| import ru.windcorp.progressia.server.world.block.BlockLogicRegistry; | ||||
| import ru.windcorp.progressia.server.world.block.TickableBlock; | ||||
| import ru.windcorp.progressia.server.world.entity.EntityLogic; | ||||
| import ru.windcorp.progressia.server.world.entity.EntityLogicRegistry; | ||||
| import ru.windcorp.progressia.server.world.tasks.TickChunk; | ||||
| import ru.windcorp.progressia.server.world.ticking.TickingPolicy; | ||||
| import ru.windcorp.progressia.server.world.tile.TickableTile; | ||||
| @@ -26,7 +24,12 @@ import ru.windcorp.progressia.server.world.tile.TileLogic; | ||||
| import ru.windcorp.progressia.server.world.tile.TileLogicRegistry; | ||||
| import ru.windcorp.progressia.server.world.tile.TileLogicStack; | ||||
|  | ||||
| public class ChunkLogic { | ||||
| public class ChunkLogic implements GenericChunk< | ||||
| 	ChunkLogic, | ||||
| 	BlockLogic, | ||||
| 	TileLogic, | ||||
| 	TileLogicStack | ||||
| > { | ||||
| 	 | ||||
| 	private final WorldLogic world; | ||||
| 	private final ChunkData data; | ||||
| @@ -43,31 +46,36 @@ public class ChunkLogic { | ||||
| 		this.world = world; | ||||
| 		this.data = data; | ||||
| 		 | ||||
| 		generateTickLists(); | ||||
| 		tmp_generateTickLists(); | ||||
| 	} | ||||
| 	 | ||||
| 	private void generateTickLists() { | ||||
| 		ChunkTickContext context = TickContextMutable.start().withChunk(this).build(); | ||||
| 	@Override | ||||
| 	public Vec3i getPosition() { | ||||
| 		return getData().getPosition(); | ||||
| 	} | ||||
| 	 | ||||
| 		context.forEachBlock(bctxt -> { | ||||
| 			BlockLogic block = bctxt.getBlock(); | ||||
| 	@Override | ||||
| 	public BlockLogic getBlock(Vec3i blockInChunk) { | ||||
| 		return BlockLogicRegistry.getInstance().get( | ||||
| 				getData().getBlock(blockInChunk).getId() | ||||
| 		); | ||||
| 	} | ||||
| 	 | ||||
| 			if (!(block instanceof TickableBlock)) return; | ||||
| 	@Override | ||||
| 	public TileLogicStack getTiles(Vec3i blockInChunk, BlockFace face) { | ||||
| 		return getTileStackWrapper(getData().getTiles(blockInChunk, face)); | ||||
| 	} | ||||
| 	 | ||||
| 			if (((TickableBlock) block).getTickingPolicy(bctxt) == TickingPolicy.REGULAR) { | ||||
| 				tickingBlocks.add(Coordinates.convertInWorldToInChunk(bctxt.getBlockInWorld(), null)); | ||||
| 			} | ||||
| 	@Override | ||||
| 	public boolean hasTiles(Vec3i blockInChunk, BlockFace face) { | ||||
| 		return getData().hasTiles(blockInChunk, face); | ||||
| 	} | ||||
| 	 | ||||
| 			bctxt.forEachFace(fctxt -> fctxt.forEachTile(tctxt -> { | ||||
| 				TileLogic tile = tctxt.getTile(); | ||||
| 				 | ||||
| 				if (!(tile instanceof TickableTile)) return; | ||||
| 				 | ||||
| 				if (((TickableTile) tile).getTickingPolicy(tctxt) == TickingPolicy.REGULAR) { | ||||
| 					tickingTiles.add(tctxt.getReference()); | ||||
| 				} | ||||
| 			})); | ||||
| 		}); | ||||
| 	private TileLogicStack getTileStackWrapper(TileDataStack tileDataList) { | ||||
| 		return tileLogicLists.computeIfAbsent( | ||||
| 				tileDataList, | ||||
| 				TileLogicStackImpl::new | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	public WorldLogic getWorld() { | ||||
| @@ -78,10 +86,6 @@ public class ChunkLogic { | ||||
| 		return data; | ||||
| 	} | ||||
| 	 | ||||
| 	public Vec3i getPosition() { | ||||
| 		return getData().getPosition(); | ||||
| 	} | ||||
| 	 | ||||
| 	public boolean hasTickingBlocks() { | ||||
| 		return !tickingBlocks.isEmpty(); | ||||
| 	} | ||||
| @@ -105,38 +109,6 @@ public class ChunkLogic { | ||||
| 		}); | ||||
| 	} | ||||
| 	 | ||||
| 	public void forEachEntity(BiConsumer<EntityLogic, EntityData> action) { | ||||
| 		getData().forEachEntity(data -> { | ||||
| 			action.accept( | ||||
| 					EntityLogicRegistry.getInstance().get(data.getId()), | ||||
| 					data | ||||
| 			); | ||||
| 		}); | ||||
| 	} | ||||
| 	 | ||||
| 	public BlockLogic getBlock(Vec3i blockInChunk) { | ||||
| 		return BlockLogicRegistry.getInstance().get( | ||||
| 				getData().getBlock(blockInChunk).getId() | ||||
| 		); | ||||
| 	} | ||||
| 	 | ||||
| 	public TileLogicStack getTiles(Vec3i blockInChunk, BlockFace face) { | ||||
| 		return getTileStackWrapper(getData().getTiles(blockInChunk, face)); | ||||
| 	} | ||||
| 	 | ||||
| 	public TileLogicStack getTilesOrNull(Vec3i blockInChunk, BlockFace face) { | ||||
| 		TileDataStack tiles = getData().getTilesOrNull(blockInChunk, face); | ||||
| 		if (tiles == null) return null; | ||||
| 		return getTileStackWrapper(tiles); | ||||
| 	} | ||||
| 	 | ||||
| 	private TileLogicStack getTileStackWrapper(TileDataStack tileDataList) { | ||||
| 		return tileLogicLists.computeIfAbsent( | ||||
| 				tileDataList, | ||||
| 				TileLogicStackImpl::new | ||||
| 		); | ||||
| 	} | ||||
| 	 | ||||
| 	public TickChunk getTickTask() { | ||||
| 		return tickTask; | ||||
| 	} | ||||
| @@ -154,11 +126,6 @@ public class ChunkLogic { | ||||
| 			return parent.getBlockInChunk(output); | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public Vec3i getChunkPos() { | ||||
| 			return ChunkLogic.this.getPosition(); | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
| 		public ChunkLogic getChunk() { | ||||
| 			return ChunkLogic.this; | ||||
| @@ -186,4 +153,28 @@ public class ChunkLogic { | ||||
| 		 | ||||
| 	} | ||||
| 	 | ||||
| 	private void tmp_generateTickLists() { | ||||
| 		ChunkTickContext context = TickContextMutable.start().withChunk(this).build(); | ||||
| 		 | ||||
| 		context.forEachBlock(bctxt -> { | ||||
| 			BlockLogic block = bctxt.getBlock(); | ||||
| 			 | ||||
| 			if (!(block instanceof TickableBlock)) return; | ||||
| 			 | ||||
| 			if (((TickableBlock) block).getTickingPolicy(bctxt) == TickingPolicy.REGULAR) { | ||||
| 				tickingBlocks.add(Coordinates.convertInWorldToInChunk(bctxt.getBlockInWorld(), null)); | ||||
| 			} | ||||
| 			 | ||||
| 			bctxt.forEachFace(fctxt -> fctxt.forEachTile(tctxt -> { | ||||
| 				TileLogic tile = tctxt.getTile(); | ||||
| 				 | ||||
| 				if (!(tile instanceof TickableTile)) return; | ||||
| 				 | ||||
| 				if (((TickableTile) tile).getTickingPolicy(tctxt) == TickingPolicy.REGULAR) { | ||||
| 					tickingTiles.add(tctxt.getReference()); | ||||
| 				} | ||||
| 			})); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -112,49 +112,6 @@ public class TickAndUpdateUtil { | ||||
| 		tickEntity(EntityLogicRegistry.getInstance().get(data.getId()), data, TickContextMutable.start().withServer(server).build()); | ||||
| 	} | ||||
| 	 | ||||
| //	public static BlockTickContext getBlockTickContext( | ||||
| //			Server server, | ||||
| //			Vec3i blockInWorld | ||||
| //	) { | ||||
| //		MutableBlockTickContext result = new MutableBlockTickContext(); | ||||
| //		result.init(server, blockInWorld); | ||||
| //		return result; | ||||
| //	} | ||||
| //	 | ||||
| //	public static TileTickContext getTileTickContext( | ||||
| //			Server server, | ||||
| //			Vec3i blockInWorld, | ||||
| //			BlockFace face, | ||||
| //			int layer | ||||
| //	) { | ||||
| //		MutableTileTickContext result = new MutableTileTickContext(); | ||||
| //		result.init(server, blockInWorld, face, layer); | ||||
| //		return result; | ||||
| //	} | ||||
| //	 | ||||
| //	public static TileTickContext getTileTickContext( | ||||
| //			Server server, | ||||
| //			TileDataStack stack, | ||||
| //			int index | ||||
| //	) { | ||||
| //		MutableTileTickContext result = new MutableTileTickContext(); | ||||
| //		result.init(server, stack, index); | ||||
| //		return result; | ||||
| //	} | ||||
| //	 | ||||
| //	public static TileTickContext getTileTickContext( | ||||
| //			Server server, | ||||
| //			TileReference ref | ||||
| //	) { | ||||
| //		MutableTileTickContext result = new MutableTileTickContext(); | ||||
| //		result.init(server, ref); | ||||
| //		return result; | ||||
| //	} | ||||
| //	 | ||||
| //	public static TickContext getTickContext(Server server) { | ||||
| //		return getBlockTickContext(server, null); | ||||
| //	} | ||||
| 	 | ||||
| 	private TickAndUpdateUtil() {} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.world.ChunkData; | ||||
| import ru.windcorp.progressia.common.world.Coordinates; | ||||
| import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
| import ru.windcorp.progressia.common.world.tile.GenericTileStack; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericTileStack; | ||||
| import ru.windcorp.progressia.common.world.tile.TileDataStack; | ||||
| import ru.windcorp.progressia.common.world.tile.TileReference; | ||||
| import ru.windcorp.progressia.server.Server; | ||||
| @@ -87,7 +87,7 @@ public abstract class TickContextMutable implements BlockTickContext, TSTickCont | ||||
| 		public static interface World extends Builder { | ||||
| 			Chunk withChunk(Vec3i chunk); | ||||
| 			Block withBlock(Vec3i blockInWorld); | ||||
| 			TileStack withTS(GenericTileStack<?, ?> tileStack); | ||||
| 			TileStack withTS(GenericTileStack<?, ?, ?> tileStack); | ||||
| 			 | ||||
| 			default Builder.Chunk withChunk(ChunkData chunk) { | ||||
| 				Objects.requireNonNull(chunk, "chunk"); | ||||
| @@ -237,7 +237,7 @@ public abstract class TickContextMutable implements BlockTickContext, TSTickCont | ||||
| 		} | ||||
| 		 | ||||
| 		@Override | ||||
| 		public TileStack withTS(GenericTileStack<?, ?> tileStack) { | ||||
| 		public TileStack withTS(GenericTileStack<?, ?, ?> tileStack) { | ||||
| 			Objects.requireNonNull(tileStack, "tileStack"); | ||||
| 			 | ||||
| 			return withBlock(tileStack.getBlockInWorld(this.blockInWorld)).withFace(tileStack.getFace()); | ||||
|   | ||||
| @@ -2,28 +2,38 @@ package ru.windcorp.progressia.server.world; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.world.ChunkData; | ||||
| import ru.windcorp.progressia.common.world.ChunkDataListeners; | ||||
| import ru.windcorp.progressia.common.world.Coordinates; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
| import ru.windcorp.progressia.common.world.WorldDataListener; | ||||
| import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericWorld; | ||||
| import ru.windcorp.progressia.server.Server; | ||||
| import ru.windcorp.progressia.server.world.block.BlockLogic; | ||||
| import ru.windcorp.progressia.server.world.tasks.TickEntitiesTask; | ||||
| import ru.windcorp.progressia.server.world.ticking.Evaluation; | ||||
| import ru.windcorp.progressia.server.world.tile.TileLogic; | ||||
| import ru.windcorp.progressia.test.TestChunkSender; | ||||
| import ru.windcorp.progressia.server.world.tile.TileLogicStack; | ||||
|  | ||||
| public class WorldLogic { | ||||
| public class WorldLogic | ||||
| implements GenericWorld< | ||||
| 	BlockLogic, | ||||
| 	TileLogic, | ||||
| 	TileLogicStack, | ||||
| 	ChunkLogic, | ||||
| 	EntityData // not using EntityLogic because it is stateless | ||||
| > { | ||||
| 	 | ||||
| 	private final WorldData data; | ||||
| 	private final Server server; | ||||
| 	 | ||||
| 	private final Map<ChunkData, ChunkLogic> chunks = new HashMap<>(); | ||||
| 	 | ||||
| 	private final Evaluation tickEntitiesTask = new TickEntitiesTask(); | ||||
| 	 | ||||
| 	public WorldLogic(WorldData data, Server server) { | ||||
| 		this.data = data; | ||||
| 		this.server = server; | ||||
| @@ -41,7 +51,25 @@ public class WorldLogic { | ||||
| 		}); | ||||
| 		 | ||||
| 		data.addListener(ChunkDataListeners.createAdder(new UpdateTriggerer(server))); | ||||
| 		data.addListener(new TestChunkSender(server)); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public ChunkLogic getChunk(Vec3i pos) { | ||||
| 		return chunks.get(getData().getChunk(pos)); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public Collection<ChunkLogic> getChunks() { | ||||
| 		return chunks.values(); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public Collection<EntityData> getEntities() { | ||||
| 		return getData().getEntities(); | ||||
| 	} | ||||
| 	 | ||||
| 	public Evaluation getTickEntitiesTask() { | ||||
| 		return tickEntitiesTask; | ||||
| 	} | ||||
| 	 | ||||
| 	public Server getServer() { | ||||
| @@ -56,52 +84,4 @@ public class WorldLogic { | ||||
| 		return chunks.get(chunkData); | ||||
| 	} | ||||
|  | ||||
| 	public ChunkLogic getChunk(Vec3i pos) { | ||||
| 		return chunks.get(getData().getChunk(pos)); | ||||
| 	} | ||||
| 	 | ||||
| 	public ChunkLogic getChunkByBlock(Vec3i blockInWorld) { | ||||
| 		return getChunk(Coordinates.convertInWorldToChunk(blockInWorld, null)); | ||||
| 	} | ||||
| 	 | ||||
| 	public BlockLogic getBlock(Vec3i blockInWorld) { | ||||
| 		ChunkLogic chunk = getChunkByBlock(blockInWorld); | ||||
| 		if (chunk == null) return null; | ||||
| 		 | ||||
| 		return chunk.getBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null)); | ||||
| 	} | ||||
| 	 | ||||
| 	public List<TileLogic> getTiles(Vec3i blockInWorld, BlockFace face) { | ||||
| 		return getTilesImpl(blockInWorld, face, true); | ||||
| 	} | ||||
| 	 | ||||
| 	public List<TileLogic> getTilesOrNull(Vec3i blockInWorld, BlockFace face) { | ||||
| 		return getTilesImpl(blockInWorld, face, false); | ||||
| 	} | ||||
| 	 | ||||
| 	private List<TileLogic> getTilesImpl(Vec3i blockInWorld, BlockFace face, boolean createIfMissing) { | ||||
| 		ChunkLogic chunk = getChunkByBlock(blockInWorld); | ||||
| 		if (chunk == null) return null; | ||||
| 		 | ||||
| 		Vec3i blockInChunk = Coordinates.convertInWorldToInChunk(blockInWorld, null); | ||||
| 		 | ||||
| 		List<TileLogic> result = | ||||
| 				createIfMissing | ||||
| 				? chunk.getTiles(blockInChunk, face) | ||||
| 				: chunk.getTilesOrNull(blockInChunk, face); | ||||
| 		 | ||||
| 		return result; | ||||
| 	} | ||||
| 	 | ||||
| 	public TileLogic getTile(Vec3i blockInWorld, BlockFace face, int layer) { | ||||
| 		List<TileLogic> tiles = getTilesOrNull(blockInWorld, face); | ||||
| 		if (tiles == null || tiles.size() <= layer) return null; | ||||
| 		 | ||||
| 		return tiles.get(layer); | ||||
| 	} | ||||
| 	 | ||||
| 	public Collection<ChunkLogic> getChunks() { | ||||
| 		return chunks.values(); | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -2,8 +2,9 @@ package ru.windcorp.progressia.server.world.block; | ||||
|  | ||||
| import ru.windcorp.progressia.common.util.namespaces.Namespaced; | ||||
| import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericBlock; | ||||
|  | ||||
| public class BlockLogic extends Namespaced { | ||||
| public class BlockLogic extends Namespaced implements GenericBlock { | ||||
|  | ||||
| 	public BlockLogic(String id) { | ||||
| 		super(id); | ||||
|   | ||||
| @@ -2,53 +2,12 @@ package ru.windcorp.progressia.server.world.tasks; | ||||
|  | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.world.Coordinates; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
| import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
| import ru.windcorp.progressia.common.world.tile.TileData; | ||||
| import ru.windcorp.progressia.common.world.tile.TileDataStack; | ||||
| import ru.windcorp.progressia.common.world.tile.PacketAddTile; | ||||
|  | ||||
| class AddTile extends CachedWorldChange { | ||||
| 	 | ||||
| 	private final Vec3i blockInWorld = new Vec3i(); | ||||
| 	private BlockFace face; | ||||
| 	private TileData tile; | ||||
| class AddTile extends CachedChunkChange<PacketAddTile> { | ||||
|  | ||||
| 	public AddTile(Consumer<? super CachedChange> disposer) { | ||||
| 		super(disposer, "Core:AddTile"); | ||||
| 	} | ||||
|  | ||||
| 	public void initialize( | ||||
| 			Vec3i position, BlockFace face, | ||||
| 			TileData tile | ||||
| 	) { | ||||
| 		if (this.tile != null) | ||||
| 			throw new IllegalStateException("Payload is not null. Current: " + this.tile + "; requested: " + tile); | ||||
| 		 | ||||
| 		this.blockInWorld.set(position.x, position.y, position.z); | ||||
| 		this.face = face; | ||||
| 		this.tile = tile; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	protected void affectCommon(WorldData world) { | ||||
| 		TileDataStack tiles = world | ||||
| 				.getChunkByBlock(blockInWorld) | ||||
| 				.getTiles(Coordinates.convertInWorldToInChunk(blockInWorld, null), face); | ||||
|  | ||||
| 		tiles.add(tile); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void getRelevantChunk(Vec3i output) { | ||||
| 		Coordinates.convertInWorldToChunk(blockInWorld, output); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void dispose() { | ||||
| 		super.dispose(); | ||||
| 		this.tile = null; | ||||
| 		super(disposer, new PacketAddTile()); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,19 @@ | ||||
| package ru.windcorp.progressia.server.world.tasks; | ||||
|  | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.world.PacketChunkChange; | ||||
|  | ||||
| public abstract class CachedChunkChange<P extends PacketChunkChange> extends CachedWorldChange<P> { | ||||
|  | ||||
| 	public CachedChunkChange(Consumer<? super CachedChange> disposer, P packet) { | ||||
| 		super(disposer, packet); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void getRelevantChunk(Vec3i output) { | ||||
| 		getPacket().getAffectedChunk(output); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -2,35 +2,49 @@ package ru.windcorp.progressia.server.world.tasks; | ||||
|  | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import ru.windcorp.progressia.common.comms.packets.PacketWorldChange; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.util.Vectors; | ||||
| import ru.windcorp.progressia.common.world.PacketWorldChange; | ||||
| import ru.windcorp.progressia.server.Server; | ||||
|  | ||||
| public abstract class CachedWorldChange extends CachedChange { | ||||
| public abstract class CachedWorldChange<P extends PacketWorldChange> extends CachedChange { | ||||
| 	 | ||||
| 	private final PacketWorldChange packet; | ||||
| 	private final P packet; | ||||
|  | ||||
| 	public CachedWorldChange(Consumer<? super CachedChange> disposer, String packetId) { | ||||
| 	public CachedWorldChange(Consumer<? super CachedChange> disposer, P packet) { | ||||
| 		super(disposer); | ||||
| 		 | ||||
| 		this.packet = new PacketWorldChange(packetId) { | ||||
| 			@Override | ||||
| 			public void apply(WorldData world) { | ||||
| 				affectCommon(world); | ||||
| 			} | ||||
| 		}; | ||||
| 		this.packet = packet; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void affect(Server server) { | ||||
| 		affectCommon(server.getWorld().getData()); | ||||
| 		server.getClientManager().broadcastGamePacket(packet); | ||||
| 		affectLocal(server); | ||||
| 		sendPacket(server); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Invoked by both Change and Packet. | ||||
| 	 * @param world the world to affect | ||||
| 	 */ | ||||
| 	protected abstract void affectCommon(WorldData world); | ||||
| 	protected void affectLocal(Server server) { | ||||
| 		packet.apply(server.getWorld().getData()); | ||||
| 	} | ||||
| 	 | ||||
| 	protected void sendPacket(Server server) { | ||||
| 		Vec3i v = Vectors.grab3i(); | ||||
| 		Vec3i chunkPos = getAffectedChunk(v); | ||||
| 		 | ||||
| 		if (chunkPos == null) { | ||||
| 			server.getClientManager().broadcastToAllPlayers(packet); | ||||
| 		} else { | ||||
| 			server.getClientManager().broadcastLocal(packet, chunkPos); | ||||
| 		} | ||||
| 		 | ||||
| 		Vectors.release(chunkPos); | ||||
| 	} | ||||
|  | ||||
| 	protected Vec3i getAffectedChunk(Vec3i output) { | ||||
| 		return null; | ||||
| 	} | ||||
| 	 | ||||
| 	public P getPacket() { | ||||
| 		return packet; | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,8 @@ | ||||
| package ru.windcorp.progressia.server.world.tasks; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.state.IOContext; | ||||
| import ru.windcorp.progressia.common.util.crash.CrashReports; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
| import ru.windcorp.progressia.common.world.entity.PacketEntityChange; | ||||
| import ru.windcorp.progressia.server.Server; | ||||
| @@ -31,26 +28,15 @@ class ChangeEntity extends CachedChange { | ||||
| 		this.entity = entity; | ||||
| 		this.change = change; | ||||
|  | ||||
| 		packet.setEntityId(entity.getEntityId()); | ||||
| 		try { | ||||
| 			entity.write(packet.getWriter(), IOContext.COMMS); // TODO wtf is this... (see whole file) | ||||
| 		} catch (IOException e) { | ||||
| 			CrashReports.report(e, "Could not write entity %s", entity); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	@SuppressWarnings("unchecked") | ||||
| 	@Override | ||||
| 	public void affect(Server server) { | ||||
| 		((StateChange<EntityData>) change).change(entity); | ||||
| 		packet.set(entity); | ||||
| 		 | ||||
| 		try { | ||||
| 			entity.write(packet.getWriter(), IOContext.COMMS); // ...and this doing at the same time? - javapony at 1 AM | ||||
| 		} catch (IOException e) { | ||||
| 			CrashReports.report(e, "Could not write entity %s", entity); | ||||
| 		} | ||||
| 		 | ||||
| 		server.getClientManager().broadcastGamePacket(packet); | ||||
| 		server.getClientManager().broadcastLocal(packet, entity.getEntityId()); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
|   | ||||
| @@ -2,43 +2,12 @@ package ru.windcorp.progressia.server.world.tasks; | ||||
|  | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.world.Coordinates; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
| import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
| import ru.windcorp.progressia.common.world.tile.TileDataStack; | ||||
| import ru.windcorp.progressia.common.world.tile.PacketRemoveTile; | ||||
|  | ||||
| class RemoveTile extends CachedWorldChange { | ||||
| 	 | ||||
| 	private final Vec3i blockInWorld = new Vec3i(); | ||||
| 	private BlockFace face; | ||||
| 	private int tag; | ||||
| class RemoveTile extends CachedChunkChange<PacketRemoveTile> { | ||||
|  | ||||
| 	public RemoveTile(Consumer<? super CachedChange> disposer) { | ||||
| 		super(disposer, "Core:RemoveTile"); | ||||
| 	} | ||||
|  | ||||
| 	public void initialize( | ||||
| 			Vec3i position, BlockFace face, | ||||
| 			int tag | ||||
| 	) { | ||||
| 		this.blockInWorld.set(position.x, position.y, position.z); | ||||
| 		this.face = face; | ||||
| 		this.tag = tag; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	protected void affectCommon(WorldData world) { | ||||
| 		TileDataStack tiles = world | ||||
| 				.getChunkByBlock(blockInWorld) | ||||
| 				.getTiles(Coordinates.convertInWorldToInChunk(blockInWorld, null), face); | ||||
|  | ||||
| 		tiles.remove(tiles.getIndexByTag(tag)); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void getRelevantChunk(Vec3i output) { | ||||
| 		Coordinates.convertInWorldToChunk(blockInWorld, output); | ||||
| 		super(disposer, new PacketRemoveTile()); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -2,44 +2,12 @@ package ru.windcorp.progressia.server.world.tasks; | ||||
|  | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.world.Coordinates; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
| import ru.windcorp.progressia.common.world.block.BlockData; | ||||
| import ru.windcorp.progressia.common.world.block.PacketSetBlock; | ||||
|  | ||||
| class SetBlock extends CachedWorldChange { | ||||
|  | ||||
| 	private final Vec3i blockInWorld = new Vec3i(); | ||||
| 	private BlockData block; | ||||
| class SetBlock extends CachedChunkChange<PacketSetBlock> { | ||||
|  | ||||
| 	public SetBlock(Consumer<? super CachedChange> disposer) { | ||||
| 		super(disposer, "Core:SetBlock"); | ||||
| 	} | ||||
|  | ||||
| 	public void initialize(Vec3i blockInWorld, BlockData block) { | ||||
| 		if (this.block != null) | ||||
| 			throw new IllegalStateException("Payload is not null. Current: " + this.block + "; requested: " + block); | ||||
| 		 | ||||
| 		this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z); | ||||
| 		this.block = block; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	protected void affectCommon(WorldData world) { | ||||
| 		world | ||||
| 			.getChunkByBlock(blockInWorld) | ||||
| 			.setBlock(Coordinates.convertInWorldToInChunk(blockInWorld, null), block, true); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void getRelevantChunk(Vec3i output) { | ||||
| 		Coordinates.convertInWorldToChunk(blockInWorld, output); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void dispose() { | ||||
| 		super.dispose(); | ||||
| 		this.block = null; | ||||
| 		super(disposer, new PacketSetBlock()); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -14,7 +14,6 @@ import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
| import ru.windcorp.progressia.common.world.tile.TileDataStack; | ||||
| import ru.windcorp.progressia.server.Server; | ||||
| import ru.windcorp.progressia.server.world.ChunkLogic; | ||||
| import ru.windcorp.progressia.server.world.TickAndUpdateUtil; | ||||
| import ru.windcorp.progressia.server.world.TickContextMutable; | ||||
| import ru.windcorp.progressia.server.world.block.BlockLogic; | ||||
| import ru.windcorp.progressia.server.world.block.TickableBlock; | ||||
| @@ -55,7 +54,6 @@ public class TickChunk extends Evaluation { | ||||
| 	public void evaluate(Server server) { | ||||
| 		tickRegulars(server); | ||||
| 		tickRandom(server); | ||||
| 		tickEntities(server); | ||||
| 	} | ||||
|  | ||||
| 	private void tickRegulars(Server server) { | ||||
| @@ -165,12 +163,6 @@ public class TickChunk extends Evaluation { | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	private void tickEntities(Server server) { | ||||
| 		chunk.getData().forEachEntity(entity -> { | ||||
| 			TickAndUpdateUtil.tickEntity(entity, server); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void getRelevantChunk(Vec3i output) { | ||||
| 		Vec3i p = chunk.getData().getPosition(); | ||||
|   | ||||
| @@ -0,0 +1,27 @@ | ||||
| package ru.windcorp.progressia.server.world.tasks; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.server.Server; | ||||
| import ru.windcorp.progressia.server.world.TickAndUpdateUtil; | ||||
| import ru.windcorp.progressia.server.world.ticking.Evaluation; | ||||
|  | ||||
| public class TickEntitiesTask extends Evaluation { | ||||
|  | ||||
| 	@Override | ||||
| 	public void evaluate(Server server) { | ||||
| 		server.getWorld().forEachEntity(entity -> { | ||||
| 			TickAndUpdateUtil.tickEntity(entity, server); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void getRelevantChunk(Vec3i output) { | ||||
| 		// Do nothing | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public boolean isThreadSensitive() { | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -38,7 +38,7 @@ public class WorldAccessor { | ||||
|  | ||||
| 	public void setBlock(Vec3i blockInWorld, BlockData block) { | ||||
| 		SetBlock change = cache.grab(SetBlock.class); | ||||
| 		change.initialize(blockInWorld, block); | ||||
| 		change.getPacket().set(block, blockInWorld); | ||||
| 		server.requestChange(change); | ||||
| 	} | ||||
| 	 | ||||
| @@ -48,7 +48,7 @@ public class WorldAccessor { | ||||
|  | ||||
| 	public void addTile(Vec3i blockInWorld, BlockFace face, TileData tile) { | ||||
| 		AddTile change = cache.grab(AddTile.class); | ||||
| 		change.initialize(blockInWorld, face, tile); | ||||
| 		change.getPacket().set(tile, blockInWorld, face); | ||||
| 		server.requestChange(change); | ||||
| 	} | ||||
| 	 | ||||
| @@ -58,7 +58,7 @@ public class WorldAccessor { | ||||
|  | ||||
| 	public void removeTile(Vec3i blockInWorld, BlockFace face, int tag) { | ||||
| 		RemoveTile change = cache.grab(RemoveTile.class); | ||||
| 		change.initialize(blockInWorld, face, tag); | ||||
| 		change.getPacket().set(blockInWorld, face, tag); | ||||
| 		server.requestChange(change); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -2,8 +2,9 @@ package ru.windcorp.progressia.server.world.tile; | ||||
|  | ||||
| import ru.windcorp.progressia.common.util.namespaces.Namespaced; | ||||
| import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericTile; | ||||
|  | ||||
| public class TileLogic extends Namespaced { | ||||
| public class TileLogic extends Namespaced implements GenericTile { | ||||
|  | ||||
| 	public TileLogic(String id) { | ||||
| 		super(id); | ||||
|   | ||||
| @@ -1,12 +1,15 @@ | ||||
| package ru.windcorp.progressia.server.world.tile; | ||||
|  | ||||
| import ru.windcorp.progressia.common.world.tile.GenericTileStack; | ||||
| import ru.windcorp.progressia.common.world.generic.GenericTileStack; | ||||
| import ru.windcorp.progressia.common.world.tile.TileDataStack; | ||||
| import ru.windcorp.progressia.server.world.ChunkLogic; | ||||
|  | ||||
| public abstract class TileLogicStack extends GenericTileStack<TileLogic, ChunkLogic> { | ||||
| 	 | ||||
| 	// TODO add @Deprecated or smth similar to all modification methods | ||||
| public abstract class TileLogicStack | ||||
| extends GenericTileStack< | ||||
| 	TileLogicStack, | ||||
| 	TileLogic, | ||||
| 	ChunkLogic | ||||
| > { | ||||
| 	 | ||||
| 	public abstract TileDataStack getData(); | ||||
|  | ||||
|   | ||||
| @@ -73,6 +73,12 @@ public class LayerTestGUI extends GUILayer { | ||||
| 				128 | ||||
| 		)); | ||||
| 		 | ||||
| 		panel.addChild(new DynamicLabel( | ||||
| 				"ChunkUpdatesDisplay", new Font().withColor(0x37A3E6).deriveShadow(), | ||||
| 				() -> "Pending updates: " + Integer.toString(ClientState.getInstance().getWorld().getPendingChunkUpdates()), | ||||
| 				128 | ||||
| 		)); | ||||
| 		 | ||||
| 		panel.getChildren().forEach(c -> { | ||||
| 			if (c instanceof Label) { | ||||
| 				labels.add((Label) c); | ||||
|   | ||||
| @@ -1,26 +1,59 @@ | ||||
| package ru.windcorp.progressia.test; | ||||
|  | ||||
| import java.io.DataInput; | ||||
| import java.io.DataInputStream; | ||||
| import java.io.DataOutput; | ||||
| import java.io.DataOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.OutputStream; | ||||
| import java.io.UncheckedIOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import gnu.trove.map.TObjectIntMap; | ||||
| import gnu.trove.map.hash.TObjectIntHashMap; | ||||
| import ru.windcorp.jputil.functions.ThrowingConsumer; | ||||
| import ru.windcorp.progressia.common.io.ChunkCodec; | ||||
| import ru.windcorp.progressia.common.world.ChunkData; | ||||
| import ru.windcorp.progressia.common.world.DecodingException; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
| import ru.windcorp.progressia.common.world.block.BlockData; | ||||
| import ru.windcorp.progressia.common.world.block.BlockDataRegistry; | ||||
| import ru.windcorp.progressia.common.world.block.BlockFace; | ||||
| import ru.windcorp.progressia.common.world.tile.TileData; | ||||
| import ru.windcorp.progressia.common.world.tile.TileDataRegistry; | ||||
|  | ||||
| public class TestChunkCodec extends ChunkCodec { | ||||
| 	 | ||||
| 	public TestChunkCodec() { | ||||
| 		super("Test:TestCodec", 0x00); | ||||
| 	private static class Palette<E> { | ||||
| 		private final List<E> nidToElement = new ArrayList<>(); | ||||
| 		private final TObjectIntMap<E> elementToNid = new TObjectIntHashMap<>(); | ||||
| 		 | ||||
| 		public void add(E element) { | ||||
| 			if (elementToNid.containsKey(element)) return; | ||||
| 			 | ||||
| 			nidToElement.add(element); | ||||
| 			elementToNid.put(element, elementToNid.size()); | ||||
| 		} | ||||
| 		 | ||||
| 		public E getByNid(int nid) { | ||||
| 			return nidToElement.get(nid); | ||||
| 		} | ||||
| 		 | ||||
| 		public int getNid(E element) { | ||||
| 			return elementToNid.get(element); | ||||
| 		} | ||||
| 		 | ||||
| 		public int size() { | ||||
| 			return nidToElement.size(); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public ChunkData decode(WorldData world, Vec3i position, InputStream data) throws DecodingException, IOException { | ||||
| 		ChunkData chunk = new ChunkData(position, world); | ||||
| 		TestContent.generateChunk(chunk); | ||||
| 		return chunk; | ||||
| 	public TestChunkCodec() { | ||||
| 		super("Test:TestCodec", 0x00); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| @@ -28,9 +61,168 @@ public class TestChunkCodec extends ChunkCodec { | ||||
| 		return true; | ||||
| 	} | ||||
| 	 | ||||
| 	/* | ||||
| 	 * Decoding | ||||
| 	 */ | ||||
|  | ||||
| 	@Override | ||||
| 	public void encode(ChunkData chunk, OutputStream output) throws IOException { | ||||
| 		// Do nothing. Heh. | ||||
| 	public ChunkData decode(WorldData world, Vec3i position, InputStream inputStream) throws DecodingException, IOException { | ||||
| 		DataInput input = new DataInputStream(inputStream); | ||||
| 		 | ||||
| 		BlockData[] blockPalette = readBlockPalette(input); | ||||
| 		TileData[] tilePalette = readTilePalette(input); | ||||
|  | ||||
| 		ChunkData chunk = new ChunkData(position, world); | ||||
| 		readBlocks(input, blockPalette, chunk); | ||||
| 		readTiles(input, tilePalette, chunk); | ||||
| 		 | ||||
| 		return chunk; | ||||
| 	} | ||||
|  | ||||
| 	private BlockData[] readBlockPalette(DataInput input) throws IOException { | ||||
| 		BlockData[] palette = new BlockData[input.readInt()]; | ||||
| 		 | ||||
| 		for (int nid = 0; nid < palette.length; ++nid) { | ||||
| 			String id = input.readUTF(); | ||||
| 			palette[nid] = BlockDataRegistry.getInstance().get(id); | ||||
| 		} | ||||
| 		 | ||||
| 		return palette; | ||||
| 	} | ||||
|  | ||||
| 	private TileData[] readTilePalette(DataInput input) throws IOException { | ||||
| 		TileData[] palette = new TileData[input.readInt()]; | ||||
| 		 | ||||
| 		for (int nid = 0; nid < palette.length; ++nid) { | ||||
| 			String id = input.readUTF(); | ||||
| 			palette[nid] = TileDataRegistry.getInstance().get(id); | ||||
| 		} | ||||
| 		 | ||||
| 		return palette; | ||||
| 	} | ||||
|  | ||||
| 	private void readBlocks(DataInput input, BlockData[] blockPalette, ChunkData chunk) throws IOException { | ||||
| 		try { | ||||
| 			chunk.forEachBiC(guard(v -> { | ||||
| 				chunk.setBlock(v, blockPalette[input.readInt()], false); | ||||
| 			})); | ||||
| 		} catch (UncheckedIOException e) { | ||||
| 			throw e.getCause(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private void readTiles(DataInput input, TileData[] tilePalette, ChunkData chunk) throws IOException { | ||||
| 		Vec3i bic = new Vec3i(); | ||||
| 		 | ||||
| 		while (true) { | ||||
| 			int xOrEndMarker = input.readByte() & 0xFF; | ||||
| 			if (xOrEndMarker == 0xFF) break; | ||||
| 			 | ||||
| 			bic.set(xOrEndMarker, input.readByte() & 0xFF, input.readByte() & 0xFF); | ||||
| 			BlockFace face = BlockFace.getFaces().get(input.readByte() & 0xFF); | ||||
| 			 | ||||
| 			int tiles = input.readByte() & 0xFF; | ||||
| 			 | ||||
| 			for (int i = 0; i < tiles; ++i) { | ||||
| 				TileData tile = tilePalette[input.readInt()]; | ||||
| 				int tag = input.readInt(); | ||||
| 				chunk.getTiles(bic, face).load(tile, tag); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	/* | ||||
| 	 * Encoding | ||||
| 	 */ | ||||
|  | ||||
| 	@Override | ||||
| 	public void encode(ChunkData chunk, OutputStream outputStream) throws IOException { | ||||
| 		 | ||||
| 		DataOutput output = new DataOutputStream(outputStream); | ||||
| 		 | ||||
| 		Palette<BlockData> blockPalette = createBlockPalette(chunk); | ||||
| 		Palette<TileData> tilePalette = createTilePalette(chunk); | ||||
| 		 | ||||
| 		writeBlockPalette(blockPalette, output); | ||||
| 		writeTilePalette(tilePalette, output); | ||||
| 		 | ||||
| 		writeBlocks(chunk, blockPalette, output); | ||||
| 		writeTiles(chunk, tilePalette, output); | ||||
| 	} | ||||
|  | ||||
| 	private Palette<BlockData> createBlockPalette(ChunkData chunk) { | ||||
| 		Palette<BlockData> blockPalette = new Palette<>(); | ||||
| 		chunk.forEachBiC(v -> blockPalette.add(chunk.getBlock(v))); | ||||
| 		return blockPalette; | ||||
| 	} | ||||
|  | ||||
| 	private Palette<TileData> createTilePalette(ChunkData chunk) { | ||||
| 		Palette<TileData> tilePalette = new Palette<>(); | ||||
| 		chunk.forEachTile((ts, t) -> tilePalette.add(t)); | ||||
| 		return tilePalette; | ||||
| 	} | ||||
|  | ||||
| 	private void writeBlockPalette(Palette<BlockData> blockPalette, DataOutput output) throws IOException { | ||||
| 		output.writeInt(blockPalette.size()); | ||||
| 		for (int nid = 0; nid < blockPalette.size(); ++nid) { | ||||
| 			BlockData block = blockPalette.getByNid(nid); | ||||
| 			output.writeUTF(block.getId()); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private void writeTilePalette(Palette<TileData> tilePalette, DataOutput output) throws IOException { | ||||
| 		output.writeInt(tilePalette.size()); | ||||
| 		for (int nid = 0; nid < tilePalette.size(); ++nid) { | ||||
| 			TileData tile = tilePalette.getByNid(nid); | ||||
| 			output.writeUTF(tile.getId()); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private void writeBlocks(ChunkData chunk, Palette<BlockData> blockPalette, DataOutput output) throws IOException { | ||||
| 		try { | ||||
| 			chunk.forEachBiC(guard(v -> { | ||||
| 				output.writeInt(blockPalette.getNid(chunk.getBlock(v))); | ||||
| 			})); | ||||
| 		} catch (UncheckedIOException e) { | ||||
| 			throw e.getCause(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private void writeTiles(ChunkData chunk, Palette<TileData> tilePalette, DataOutput output) throws IOException { | ||||
| 		Vec3i bic = new Vec3i(); | ||||
| 		 | ||||
| 		try { | ||||
| 			chunk.forEachTileStack(guard(ts -> { | ||||
| 				if (ts.isEmpty()) return; | ||||
| 				 | ||||
| 				ts.getBlockInChunk(bic); | ||||
| 				output.writeByte(bic.x); | ||||
| 				output.writeByte(bic.y); | ||||
| 				output.writeByte(bic.z); | ||||
| 				 | ||||
| 				output.writeByte(ts.getFace().getId()); | ||||
| 				output.writeByte(ts.size()); | ||||
| 				 | ||||
| 				for (int index = 0; index < ts.size(); ++index) { | ||||
| 					output.writeInt(tilePalette.getNid(ts.get(index))); | ||||
| 					output.writeInt(ts.getTagByIndex(index)); | ||||
| 				} | ||||
| 			})); | ||||
| 		} catch (UncheckedIOException e) { | ||||
| 			throw e.getCause(); | ||||
| 		} | ||||
| 		 | ||||
| 		output.writeByte(0xFF); | ||||
| 	} | ||||
| 	 | ||||
| 	private static <V> Consumer<V> guard(ThrowingConsumer<? super V, IOException> action) { | ||||
| 		return v -> { | ||||
| 			try { | ||||
| 				action.accept(v); | ||||
| 			} catch (IOException e) { | ||||
| 				throw new UncheckedIOException(e); | ||||
| 			} | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,55 +0,0 @@ | ||||
| package ru.windcorp.progressia.test; | ||||
|  | ||||
| import java.io.IOException; | ||||
|  | ||||
| import glm.Glm; | ||||
| import ru.windcorp.progressia.common.comms.packets.PacketLoadChunk; | ||||
| import ru.windcorp.progressia.common.comms.packets.PacketSetLocalPlayer; | ||||
| import ru.windcorp.progressia.common.io.ChunkIO; | ||||
| import ru.windcorp.progressia.common.util.crash.CrashReports; | ||||
| import ru.windcorp.progressia.common.world.ChunkData; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
| import ru.windcorp.progressia.common.world.WorldDataListener; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
| import ru.windcorp.progressia.server.Server; | ||||
|  | ||||
| public class TestChunkSender implements WorldDataListener { | ||||
| 	 | ||||
| 	private final Server server; | ||||
| 	 | ||||
| 	public TestChunkSender(Server server) { | ||||
| 		this.server = server; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void onChunkLoaded(WorldData world, ChunkData chunk) { | ||||
| 		PacketLoadChunk packet = new PacketLoadChunk("Core:LoadChunk"); | ||||
| 		 | ||||
| 		packet.getPosition().set( | ||||
| 				chunk.getPosition().x, | ||||
| 				chunk.getPosition().y, | ||||
| 				chunk.getPosition().z | ||||
| 		); | ||||
| 		 | ||||
| 		try { | ||||
| 			ChunkIO.save(chunk, packet.getData().getOutputStream()); | ||||
| 		} catch (IOException e) { | ||||
| 			CrashReports.report(e, "TestChunkSender fjcked up. javahorse stupid"); | ||||
| 		} | ||||
| 		 | ||||
| 		server.getClientManager().broadcastGamePacket(packet); | ||||
| 		 | ||||
| 		tmp_sendPlayerIfPossible(world, chunk); | ||||
| 	} | ||||
|  | ||||
| 	private void tmp_sendPlayerIfPossible(WorldData world, ChunkData chunk) { | ||||
| 		EntityData e = world.getEntity(TestContent.PLAYER_ENTITY_ID); | ||||
| 		if (e == null) return; | ||||
| 		 | ||||
| 		if (Glm.equals(e.getChunkCoords(null), chunk.getPosition())) { | ||||
| 			System.out.printf("TestChunkSender: player found in (%d; %d; %d)\n", e.getChunkCoords(null).x, e.getChunkCoords(null).y, e.getChunkCoords(null).z); | ||||
| 			server.getClientManager().broadcastGamePacket(new PacketSetLocalPlayer(e.getEntityId())); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -7,16 +7,12 @@ import java.util.function.Consumer; | ||||
|  | ||||
| import org.lwjgl.glfw.GLFW; | ||||
|  | ||||
| import glm.Glm; | ||||
| import glm.vec._2.Vec2; | ||||
| import glm.vec._3.Vec3; | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.client.ClientState; | ||||
| import ru.windcorp.progressia.client.audio.SoundEffect; | ||||
| import ru.windcorp.progressia.client.comms.controls.*; | ||||
| import ru.windcorp.progressia.client.graphics.input.KeyEvent; | ||||
| import ru.windcorp.progressia.client.graphics.input.KeyMatcher; | ||||
| import ru.windcorp.progressia.client.graphics.world.LocalPlayer; | ||||
| import ru.windcorp.progressia.client.graphics.world.Selection; | ||||
| import ru.windcorp.progressia.client.world.block.*; | ||||
| import ru.windcorp.progressia.client.world.entity.*; | ||||
| @@ -26,7 +22,6 @@ import ru.windcorp.progressia.common.collision.CollisionModel; | ||||
| import ru.windcorp.progressia.common.comms.controls.*; | ||||
| import ru.windcorp.progressia.common.io.ChunkIO; | ||||
| import ru.windcorp.progressia.common.state.StatefulObjectRegistry.Factory; | ||||
| import ru.windcorp.progressia.common.util.Vectors; | ||||
| import ru.windcorp.progressia.common.world.ChunkData; | ||||
| import ru.windcorp.progressia.common.world.Coordinates; | ||||
| import ru.windcorp.progressia.common.world.block.*; | ||||
| @@ -214,12 +209,9 @@ public class TestContent { | ||||
| 	 | ||||
| 	private static Selection getSelection() { | ||||
| 		ru.windcorp.progressia.client.Client client = ClientState.getInstance(); | ||||
| 		if (client == null) return null; | ||||
| 		if (client == null || !client.isReady()) return null; | ||||
| 		 | ||||
| 		LocalPlayer player = client.getLocalPlayer(); | ||||
| 		if (player == null) return null; | ||||
| 		 | ||||
| 		return player.getSelection(); | ||||
| 		return client.getLocalPlayer().getSelection(); | ||||
| 	} | ||||
| 	 | ||||
| 	private static void onBlockBreakTrigger(ControlData control) { | ||||
| @@ -336,21 +328,6 @@ public class TestContent { | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		 | ||||
| 		if (Glm.equals(chunk.getPosition(), Vectors.ZERO_3i)) { | ||||
| 			EntityData player = EntityDataRegistry.getInstance().create("Test:Player"); | ||||
| 			player.setEntityId(PLAYER_ENTITY_ID); | ||||
| 			player.setPosition(new Vec3(8, 8, 8)); | ||||
| 			player.setDirection(new Vec2( | ||||
| 					(float) Math.toRadians(40), (float) Math.toRadians(45) | ||||
| 			)); | ||||
| 			chunk.getEntities().add(player); | ||||
| 			 | ||||
| 			EntityData statie = EntityDataRegistry.getInstance().create("Test:Statie"); | ||||
| 			statie.setEntityId(STATIE_ENTITY_ID); | ||||
| 			statie.setPosition(new Vec3(0, 15, 16)); | ||||
| 			chunk.getEntities().add(statie); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private static void registerMisc() { | ||||
|   | ||||
| @@ -17,6 +17,7 @@ import ru.windcorp.progressia.client.graphics.world.LocalPlayer; | ||||
| import ru.windcorp.progressia.common.Units; | ||||
| import ru.windcorp.progressia.common.util.FloatMathUtils; | ||||
| import ru.windcorp.progressia.common.world.entity.EntityData; | ||||
| import ru.windcorp.progressia.server.ServerState; | ||||
|  | ||||
| public class TestPlayerControls { | ||||
| 	 | ||||
| @@ -28,8 +29,8 @@ public class TestPlayerControls { | ||||
| 	 | ||||
| 	private TestPlayerControls() {} | ||||
| 	 | ||||
| 	private static final double MODE_SWITCH_MAX_DELAY = 100 * Units.MILLISECONDS; | ||||
| 	private static final double MIN_JUMP_DELAY = 200 * Units.MILLISECONDS; | ||||
| 	private static final double MODE_SWITCH_MAX_DELAY = 300 * Units.MILLISECONDS; | ||||
| 	private static final double MIN_JUMP_DELAY = 400 * Units.MILLISECONDS; | ||||
|  | ||||
| 	// Horizontal and vertical max control speed when flying | ||||
| 	private static final float FLYING_SPEED = 6.0f * Units.METERS_PER_SECOND; | ||||
| @@ -60,7 +61,7 @@ public class TestPlayerControls { | ||||
| 	private Runnable updateCallback = null; | ||||
| 	 | ||||
| 	public void applyPlayerControls() { | ||||
| 		if (ClientState.getInstance() == null || ClientState.getInstance().getLocalPlayer() == null) { | ||||
| 		if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) { | ||||
| 			return; | ||||
| 		} | ||||
| 		 | ||||
| @@ -96,10 +97,17 @@ public class TestPlayerControls { | ||||
| 		} | ||||
| 		 | ||||
| 		player.getVelocity().set(change); | ||||
| 		 | ||||
| 		// THIS IS TERRIBLE TEST | ||||
| 		EntityData serverEntity = ServerState.getInstance().getWorld().getData().getEntity(TestContent.PLAYER_ENTITY_ID); | ||||
| 		if (serverEntity != null) { | ||||
| 			serverEntity.setPosition(player.getPosition()); | ||||
| 		} | ||||
| 		 | ||||
| 	} | ||||
| 	 | ||||
| 	public void handleInput(Input input) { | ||||
| 		if (ClientState.getInstance() == null || ClientState.getInstance().getLocalPlayer() == null) { | ||||
| 		if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) { | ||||
| 			return; | ||||
| 		} | ||||
| 		 | ||||
|   | ||||
| @@ -0,0 +1,96 @@ | ||||
| package ru.windcorp.progressia.test; | ||||
|  | ||||
| import java.io.BufferedInputStream; | ||||
| import java.io.BufferedOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.OutputStream; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.nio.file.Paths; | ||||
| import java.util.zip.DeflaterOutputStream; | ||||
| import java.util.zip.InflaterInputStream; | ||||
|  | ||||
| import org.apache.logging.log4j.LogManager; | ||||
| import org.apache.logging.log4j.Logger; | ||||
|  | ||||
| import glm.vec._3.i.Vec3i; | ||||
| import ru.windcorp.progressia.common.io.ChunkIO; | ||||
| import ru.windcorp.progressia.common.world.ChunkData; | ||||
| import ru.windcorp.progressia.common.world.DecodingException; | ||||
| import ru.windcorp.progressia.common.world.WorldData; | ||||
|  | ||||
| public class TestWorldDiskIO { | ||||
| 	 | ||||
| 	private static final Path SAVE_DIR = Paths.get("tmp_world"); | ||||
| 	private static final Logger LOG = LogManager.getLogger("TestWorldDiskIO"); | ||||
| 	 | ||||
| 	private static final boolean ENABLE = true; | ||||
|  | ||||
| 	public static void saveChunk(ChunkData chunk) { | ||||
| 		if (!ENABLE) return; | ||||
| 		 | ||||
| 		try { | ||||
| 			LOG.debug( | ||||
| 					"Saving {} {} {}", | ||||
| 					chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z | ||||
| 			); | ||||
| 			 | ||||
| 			Files.createDirectories(SAVE_DIR); | ||||
| 			 | ||||
| 			Path path = SAVE_DIR.resolve(String.format( | ||||
| 					"chunk_%+d_%+d_%+d.progressia_chunk", | ||||
| 					chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z | ||||
| 			)); | ||||
| 			 | ||||
| 			try (OutputStream output = new DeflaterOutputStream(new BufferedOutputStream(Files.newOutputStream(path)))) { | ||||
| 				ChunkIO.save(chunk, output); | ||||
| 			} | ||||
| 		} catch (IOException e) { | ||||
| 			e.printStackTrace(); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	public static ChunkData tryToLoad(Vec3i chunkPos, WorldData world) { | ||||
| 		if (!ENABLE) return null; | ||||
| 		 | ||||
| 		Path path = SAVE_DIR.resolve(String.format( | ||||
| 				"chunk_%+d_%+d_%+d.progressia_chunk", | ||||
| 				chunkPos.x, chunkPos.y, chunkPos.z | ||||
| 		)); | ||||
| 		 | ||||
| 		if (!Files.exists(path)) { | ||||
| 			LOG.debug( | ||||
| 					"Not found {} {} {}", | ||||
| 					chunkPos.x, chunkPos.y, chunkPos.z | ||||
| 			); | ||||
| 			 | ||||
| 			return null; | ||||
| 		} | ||||
| 		 | ||||
| 		try { | ||||
| 			ChunkData result = load(path, chunkPos, world); | ||||
| 			 | ||||
| 			LOG.debug( | ||||
| 					"Loaded {} {} {}", | ||||
| 					chunkPos.x, chunkPos.y, chunkPos.z | ||||
| 			); | ||||
| 			 | ||||
| 			return result; | ||||
| 		} catch (Exception e) { | ||||
| 			e.printStackTrace(); | ||||
| 			LOG.debug( | ||||
| 					"Could not load {} {} {}", | ||||
| 					chunkPos.x, chunkPos.y, chunkPos.z | ||||
| 			); | ||||
| 			return null; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private static ChunkData load(Path path, Vec3i chunkPos, WorldData world) throws IOException, DecodingException { | ||||
| 		try (InputStream input = new InflaterInputStream(new BufferedInputStream(Files.newInputStream(path)))) { | ||||
| 			return ChunkIO.load(world, chunkPos, input); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| } | ||||
		Reference in New Issue
	
	Block a user