diff --git a/src/main/java/ru/windcorp/progressia/client/audio/backend/AudioReader.java b/src/main/java/ru/windcorp/progressia/client/audio/backend/AudioReader.java index 8cc2eff..2ea235b 100644 --- a/src/main/java/ru/windcorp/progressia/client/audio/backend/AudioReader.java +++ b/src/main/java/ru/windcorp/progressia/client/audio/backend/AudioReader.java @@ -1,7 +1,6 @@ package ru.windcorp.progressia.client.audio.backend; import org.lwjgl.BufferUtils; -import ru.windcorp.progressia.client.audio.backend.SoundType; import ru.windcorp.progressia.common.resource.*; import java.nio.IntBuffer; diff --git a/src/main/java/ru/windcorp/progressia/client/audio/backend/SoundType.java b/src/main/java/ru/windcorp/progressia/client/audio/backend/SoundType.java index 18e32e0..f382dd2 100644 --- a/src/main/java/ru/windcorp/progressia/client/audio/backend/SoundType.java +++ b/src/main/java/ru/windcorp/progressia/client/audio/backend/SoundType.java @@ -26,15 +26,6 @@ public class SoundType extends Namespaced { alBufferData(audioBuffer, format, rawAudio, sampleRate); } - //TODO What is this (Eugene Smirnov) - private Speaker createSound(int source, int audio) { - if (!alIsBuffer(audio) || !alIsSource(source)) - throw new RuntimeException(); - - alBufferData(audio, format, rawAudio, sampleRate); - return new Speaker(audio); - } - public void initSpeaker(Speaker speaker) { speaker.setAudioData(audioBuffer); } diff --git a/src/main/java/ru/windcorp/progressia/common/util/crash/providers/ArgsContextProvider.java b/src/main/java/ru/windcorp/progressia/common/util/crash/providers/ArgsContextProvider.java index e7c9e64..317a90a 100644 --- a/src/main/java/ru/windcorp/progressia/common/util/crash/providers/ArgsContextProvider.java +++ b/src/main/java/ru/windcorp/progressia/common/util/crash/providers/ArgsContextProvider.java @@ -1,6 +1,5 @@ package ru.windcorp.progressia.common.util.crash.providers; -import ru.windcorp.progressia.Progressia; import ru.windcorp.progressia.ProgressiaLauncher; import ru.windcorp.progressia.common.util.crash.ContextProvider; diff --git a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java index d725193..4a9d5d5 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java @@ -210,7 +210,11 @@ public class ChunkData { private void createNormalTileContainer(Vec3i blockInChunk, BlockFace face) { List primaryList = SizeLimitedList.wrap( - new ArrayList<>(TILES_PER_FACE), TILES_PER_FACE + new ChunkDataReportingList( + new ArrayList<>(TILES_PER_FACE), + this, blockInChunk, face + ), + TILES_PER_FACE ); List secondaryList = Lists.reverse(primaryList); diff --git a/src/main/java/ru/windcorp/progressia/common/world/ChunkDataReportingList.java b/src/main/java/ru/windcorp/progressia/common/world/ChunkDataReportingList.java new file mode 100644 index 0000000..bbe16c4 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkDataReportingList.java @@ -0,0 +1,69 @@ +package ru.windcorp.progressia.common.world; + +import java.util.AbstractList; +import java.util.List; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.tile.TileData; + +class ChunkDataReportingList extends AbstractList { + + private final List parent; + private final ChunkData reportTo; + private final Vec3i blockInChunk; + private final BlockFace face; + + public ChunkDataReportingList(List parent, ChunkData reportTo, Vec3i blockInChunk, BlockFace face) { + super(); + this.parent = parent; + this.reportTo = reportTo; + this.blockInChunk = new Vec3i(blockInChunk); + this.face = face; + } + + @Override + public TileData get(int index) { + return parent.get(index); + } + + @Override + public int size() { + return parent.size(); + } + + @Override + public TileData set(int index, TileData element) { + TileData previous = parent.set(index, element); + report(previous, element); + return previous; + } + + @Override + public void add(int index, TileData element) { + parent.add(index, element); + report(null, element); + } + + @Override + public TileData remove(int index) { + TileData previous = parent.remove(index); + report(previous, null); + return previous; + } + + private void report(TileData previous, TileData current) { + reportTo.getListeners().forEach(l -> { + if (previous != null) { + l.onChunkTilesChanged(reportTo, blockInChunk, face, previous, false); + } + + if (current != null) { + l.onChunkTilesChanged(reportTo, blockInChunk, face, current, true); + } + + l.onChunkChanged(reportTo); + }); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/block/BlockFace.java b/src/main/java/ru/windcorp/progressia/common/world/block/BlockFace.java index 461429e..7fa3d26 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/block/BlockFace.java +++ b/src/main/java/ru/windcorp/progressia/common/world/block/BlockFace.java @@ -120,6 +120,15 @@ public final class BlockFace extends BlockRelation { return counterFace; } + public BlockFace getCounter() { + return counterFace; + } + + public BlockFace getCounterAndMoveCursor(Vec3i cursor) { + cursor.add(getVector()); + return counterFace; + } + public int getId() { return id; } diff --git a/src/main/java/ru/windcorp/progressia/server/Server.java b/src/main/java/ru/windcorp/progressia/server/Server.java index 66c81a8..066cb26 100644 --- a/src/main/java/ru/windcorp/progressia/server/Server.java +++ b/src/main/java/ru/windcorp/progressia/server/Server.java @@ -1,12 +1,18 @@ package ru.windcorp.progressia.server; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.function.Consumer; + import ru.windcorp.jputil.functions.ThrowingRunnable; import ru.windcorp.progressia.common.util.TaskQueue; import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.server.comms.ClientManager; -import ru.windcorp.progressia.server.world.Changer; -import ru.windcorp.progressia.server.world.ImplementedChangeTracker; import ru.windcorp.progressia.server.world.WorldLogic; +import ru.windcorp.progressia.server.world.tasks.WorldAccessor; +import ru.windcorp.progressia.server.world.ticking.Change; +import ru.windcorp.progressia.server.world.ticking.Evaluation; public class Server { @@ -15,18 +21,20 @@ public class Server { } private final WorldLogic world; - private final ImplementedChangeTracker adHocChanger = - new ImplementedChangeTracker(); + private final WorldAccessor worldAccessor = new WorldAccessor(this); private final ServerThread serverThread; private final ClientManager clientManager = new ClientManager(this); private final TaskQueue taskQueue = new TaskQueue(this::isServerThread); + private final Collection> repeatingTasks = Collections.synchronizedCollection(new ArrayList<>()); public Server(WorldData world) { - this.world = new WorldLogic(world); + this.world = new WorldLogic(world, this); this.serverThread = new ServerThread(this); + + invokeEveryTick(this::scheduleChunkTicks); } public WorldLogic getWorld() { @@ -34,24 +42,52 @@ public class Server { } /** - * Do not use in ticks + * Returns this server's {@link ClientManager}. + * Use this to deal with communications, e.g. send packets. + * @return the {@link ClientManager} that handles this server */ - public Changer getAdHocChanger() { - return adHocChanger; - } - public ClientManager getClientManager() { return clientManager; } + /** + * Checks if this thread is the main thread of this server. + * @return {@code true} iff the invocation occurs in server main thread + */ public boolean isServerThread() { return getCurrentServer() == this; } + /** + * Requests that the provided task is executed once on next server tick. + * The task will be run in the main server thread. The task object is + * discarded after execution. + * + *

Use this method to request a one-time (rare) action that must necessarily + * happen in the main server thread, such as initialization tasks or reconfiguration. + * + * @param task the task to run + * @see #invokeNow(Runnable) + * @see #invokeEveryTick(Consumer) + */ public void invokeLater(Runnable task) { taskQueue.invokeLater(task); } + /** + * Executes the tasks in the server main thread as soon as possible. + * + *

If this method is invoked in the server main thread, then the task is + * run immediately (the method blocks until the task finishes). Otherwise + * this method behaves exactly like {@link #invokeLater(Runnable)}. + * + *

Use this method to make sure that a piece of code is run in the main server + * thread. + * + * @param task the task to run + * @see #invokeLater(Runnable) + * @see #invokeEveryTick(Consumer) + */ public void invokeNow(Runnable task) { taskQueue.invokeNow(task); } @@ -61,6 +97,26 @@ public class Server { ) throws InterruptedException, E { taskQueue.waitAndInvoke(task); } + + public void invokeEveryTick(Consumer task) { + repeatingTasks.add(task); + } + + public void requestChange(Change change) { + serverThread.getTicker().requestChange(change); + } + + public void requestEvaluation(Evaluation evaluation) { + serverThread.getTicker().requestEvaluation(evaluation); + } + + public double getTickLength() { + return this.serverThread.getTicker().getTickLength(); + } + + public WorldAccessor getWorldAccessor() { + return worldAccessor; + } public void start() { this.serverThread.start(); @@ -68,11 +124,15 @@ public class Server { public void tick() { taskQueue.runTasks(); - adHocChanger.applyChanges(this); + repeatingTasks.forEach(t -> t.accept(this)); } public void shutdown(String message) { // Do nothing } + + private void scheduleChunkTicks(Server server) { + + } } diff --git a/src/main/java/ru/windcorp/progressia/server/ServerThread.java b/src/main/java/ru/windcorp/progressia/server/ServerThread.java index ab980c0..e28c63a 100644 --- a/src/main/java/ru/windcorp/progressia/server/ServerThread.java +++ b/src/main/java/ru/windcorp/progressia/server/ServerThread.java @@ -3,10 +3,9 @@ package ru.windcorp.progressia.server; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.LogManager; -import ru.windcorp.progressia.server.world.Ticker; +import ru.windcorp.progressia.server.world.ticking.TickerCoordinator; public class ServerThread implements Runnable { @@ -39,14 +38,15 @@ public class ServerThread implements Runnable { r -> new Thread(new ServerThreadTracker(r), "Server thread") ); - private final Ticker ticker; + private final TickerCoordinator ticker; public ServerThread(Server server) { this.server = server; - this.ticker = new Ticker(server); + this.ticker = new TickerCoordinator(server, 1); } public void start() { + ticker.start(); executor.scheduleAtFixedRate(this, 0, 1000 / 20, TimeUnit.MILLISECONDS); } @@ -54,14 +54,18 @@ public class ServerThread implements Runnable { public void run() { try { server.tick(); - ticker.run(); + ticker.runOneTick(); } catch (Exception e) { LogManager.getLogger(getClass()).error("Got an exception in server thread", e); } } - + public Server getServer() { return server; } + + public TickerCoordinator getTicker() { + return ticker; + } } diff --git a/src/main/java/ru/windcorp/progressia/server/world/Changer.java b/src/main/java/ru/windcorp/progressia/server/world/Changer.java deleted file mode 100644 index e4aee3d..0000000 --- a/src/main/java/ru/windcorp/progressia/server/world/Changer.java +++ /dev/null @@ -1,24 +0,0 @@ -package ru.windcorp.progressia.server.world; - -import glm.vec._3.i.Vec3i; -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.tile.TileData; - -public interface Changer { - - @FunctionalInterface - public static interface Change { - void change(T object); - } - - void setBlock(Vec3i pos, BlockData block); - - void addTile(Vec3i block, BlockFace face, TileData tile); - - void removeTile(Vec3i block, BlockFace face, TileData tile); - - void changeEntity(T entity, Change change); - -} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/server/world/ChunkLogic.java b/src/main/java/ru/windcorp/progressia/server/world/ChunkLogic.java index 6ba1087..f2001ae 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/ChunkLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/ChunkLogic.java @@ -12,7 +12,9 @@ import com.google.common.collect.Lists; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.client.world.tile.TileLocation; +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.BlockFace; import ru.windcorp.progressia.common.world.tile.TileData; import ru.windcorp.progressia.server.world.block.BlockLogic; @@ -47,34 +49,33 @@ public class ChunkLogic { MutableTileTickContext tileTickContext = new MutableTileTickContext(); - blockTickContext.setWorld(getWorld()); - blockTickContext.setChunk(this); - - tileTickContext.setWorld(getWorld()); - tileTickContext.setChunk(this); - data.forEachBlock(blockInChunk -> { BlockLogic block = getBlock(blockInChunk); if (block instanceof TickableBlock) { - blockTickContext.setCoordsInChunk(blockInChunk); + Vec3i blockInWorld = Vectors.grab3i(); + Coordinates.getInWorld(getData().getPosition(), blockInChunk, blockInWorld); - if (((TickableBlock) block) - .doesTickRegularly(blockTickContext) - ) { + blockTickContext.init(getWorld().getServer(), blockInWorld); + + Vectors.release(blockInWorld); + + if (((TickableBlock) block).doesTickRegularly(blockTickContext)) { tickingBlocks.add(new Vec3i(blockInChunk)); } } }); data.forEachTile((loc, tileData) -> { - TileLogic tile = - TileLogicRegistry.getInstance().get(tileData.getId()); + TileLogic tile = TileLogicRegistry.getInstance().get(tileData.getId()); if (tile instanceof TickableTile) { - tileTickContext.setCoordsInChunk(loc.pos); - tileTickContext.setFace(loc.face); - tileTickContext.setLayer(loc.layer); + Vec3i blockInWorld = Vectors.grab3i(); + Coordinates.getInWorld(getData().getPosition(), loc.pos, blockInWorld); + + tileTickContext.init(getWorld().getServer(), blockInWorld, loc.face, loc.layer); + + Vectors.release(blockInWorld); if (((TickableTile) tile).doesTickRegularly(tileTickContext)) { tickingTiles.add(new TileLocation(loc)); @@ -109,8 +110,7 @@ public class ChunkLogic { tickingTiles.forEach(location -> { action.accept( location, - getTilesOrNull(location.pos, location.face) - .get(location.layer) + getTilesOrNull(location.pos, location.face).get(location.layer) ); }); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/ImplementedChangeTracker.java b/src/main/java/ru/windcorp/progressia/server/world/ImplementedChangeTracker.java deleted file mode 100644 index 3cb6a8b..0000000 --- a/src/main/java/ru/windcorp/progressia/server/world/ImplementedChangeTracker.java +++ /dev/null @@ -1,230 +0,0 @@ -package ru.windcorp.progressia.server.world; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import glm.vec._3.i.Vec3i; -import ru.windcorp.progressia.common.comms.packets.Packet; -import ru.windcorp.progressia.common.comms.packets.PacketWorldChange; -import ru.windcorp.progressia.common.state.IOContext; -import ru.windcorp.progressia.common.util.LowOverheadCache; -import ru.windcorp.progressia.common.util.Vectors; -import ru.windcorp.progressia.common.util.crash.CrashReports; -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.BlockFace; -import ru.windcorp.progressia.common.world.entity.EntityData; -import ru.windcorp.progressia.common.world.entity.PacketEntityChange; -import ru.windcorp.progressia.common.world.tile.TileData; -import ru.windcorp.progressia.server.Server; - -public class ImplementedChangeTracker implements Changer { - - public static interface ChangeImplementation { - void applyOnServer(WorldData world); - Packet asPacket(); - } - - private static class SetBlock - extends PacketWorldChange - implements ChangeImplementation { - - private final Vec3i position = new Vec3i(); - private BlockData block; - - public SetBlock() { - this("Core:SetBlock"); - } - - protected SetBlock(String id) { - super(id); - } - - public void initialize(Vec3i position, BlockData block) { - this.position.set(position.x, position.y, position.z); - this.block = block; - } - - @Override - public void applyOnServer(WorldData world) { - Vec3i blockInChunk = Vectors.grab3i(); - Coordinates.convertInWorldToInChunk(position, blockInChunk); - - world.getChunkByBlock(position).setBlock(blockInChunk, block, true); - - Vectors.release(blockInChunk); - } - - @Override - public void apply(WorldData world) { - applyOnServer(world); - } - - @Override - public Packet asPacket() { - return this; - } - - } - - private static class AddOrRemoveTile - extends PacketWorldChange - implements ChangeImplementation { - - private final Vec3i position = new Vec3i(); - private BlockFace face; - private TileData tile; - - private boolean shouldAdd; - - public AddOrRemoveTile() { - this("Core:AddOrRemoveTile"); - } - - protected AddOrRemoveTile(String id) { - super(id); - } - - public void initialize( - Vec3i position, BlockFace face, - TileData tile, - boolean shouldAdd - ) { - this.position.set(position.x, position.y, position.z); - this.face = face; - this.tile = tile; - this.shouldAdd = shouldAdd; - } - - @Override - public void applyOnServer(WorldData world) { - Vec3i blockInChunk = Vectors.grab3i(); - Coordinates.convertInWorldToInChunk(position, blockInChunk); - - List tiles = world.getChunkByBlock(position).getTiles(blockInChunk, face); - - if (shouldAdd) { - tiles.add(tile); - } else { - tiles.remove(tile); - } - - Vectors.release(blockInChunk); - } - - @Override - public void apply(WorldData world) { - applyOnServer(world); - } - - @Override - public Packet asPacket() { - return this; - } - - } - - private static class ChangeEntity implements ChangeImplementation { - - private EntityData entity; - private Change change; - - private final PacketEntityChange packet = new PacketEntityChange(); - - public void set(T entity, Change change) { - this.entity = entity; - this.change = change; - - packet.setEntityId(entity.getEntityId()); - try { - entity.write(packet.getWriter(), IOContext.COMMS); - } catch (IOException e) { - CrashReports.report(e, "Could not write entity %s", entity); - } - } - - @SuppressWarnings("unchecked") - @Override - public void applyOnServer(WorldData world) { - ((Change) change).change(entity); - - try { - entity.write(packet.getWriter(), IOContext.COMMS); - } catch (IOException e) { - CrashReports.report(e, "Could not write entity %s", entity); - } - } - - @Override - public Packet asPacket() { - return packet; - } - - } - - private final List changes = new ArrayList<>(1024); - - private final LowOverheadCache setBlockCache = - new LowOverheadCache<>(SetBlock::new); - - private final LowOverheadCache addOrRemoveTileCache = - new LowOverheadCache<>(AddOrRemoveTile::new); - - private final LowOverheadCache changeEntityCache = - new LowOverheadCache<>(ChangeEntity::new); - - @Override - public void setBlock(Vec3i pos, BlockData block) { - SetBlock change = setBlockCache.grab(); - change.initialize(pos, block); - changes.add(change); - } - - @Override - public void addTile(Vec3i block, BlockFace face, TileData tile) { - AddOrRemoveTile change = addOrRemoveTileCache.grab(); - change.initialize(block, face, tile, true); - changes.add(change); - } - - @Override - public void removeTile(Vec3i block, BlockFace face, TileData tile) { - AddOrRemoveTile change = addOrRemoveTileCache.grab(); - change.initialize(block, face, tile, false); - changes.add(change); - } - - @Override - public void changeEntity( - T entity, Change change - ) { - ChangeEntity changeRecord = changeEntityCache.grab(); - changeRecord.set(entity, change); - changes.add(changeRecord); - } - - public void applyChanges(Server server) { - changes.forEach(c -> c.applyOnServer(server.getWorld().getData())); - changes.stream().map(ChangeImplementation::asPacket).filter(Objects::nonNull).forEach( - server.getClientManager()::broadcastGamePacket - ); - changes.forEach(this::release); - changes.clear(); - } - - private void release(ChangeImplementation c) { - if (c instanceof SetBlock) { - setBlockCache.release((SetBlock) c); - } else if (c instanceof AddOrRemoveTile) { - addOrRemoveTileCache.release((AddOrRemoveTile) c); - } else if (c instanceof ChangeEntity) { - changeEntityCache.release((ChangeEntity) c); - } else { - throw new IllegalArgumentException("Could not find cache for " + c); - } - } - -} diff --git a/src/main/java/ru/windcorp/progressia/server/world/MutableBlockTickContext.java b/src/main/java/ru/windcorp/progressia/server/world/MutableBlockTickContext.java index b23731a..b1618d9 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/MutableBlockTickContext.java +++ b/src/main/java/ru/windcorp/progressia/server/world/MutableBlockTickContext.java @@ -3,41 +3,42 @@ package ru.windcorp.progressia.server.world; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.world.Coordinates; +import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.world.block.BlockTickContext; public class MutableBlockTickContext -extends MutableChunkTickContext +extends MutableTickContext implements BlockTickContext { private final Vec3i blockInWorld = new Vec3i(); - private final Vec3i blockInChunk = new Vec3i(); + private ChunkLogic chunk; @Override - public Vec3i getCoords() { + public Vec3i getBlockInWorld() { return this.blockInWorld; } - @Override - public Vec3i getChunkCoords() { - return this.blockInChunk; - } - - public void setCoordsInWorld(Vec3i coords) { - getCoords().set(coords.x, coords.y, coords.z); - Coordinates.convertInWorldToInChunk(getCoords(), getChunkCoords()); + public void setCoordsInWorld(Vec3i blockInWorld) { + getBlockInWorld().set(blockInWorld.x, blockInWorld.y, blockInWorld.z); Vec3i chunk = Vectors.grab3i(); - Coordinates.convertInWorldToChunk(coords, chunk); + Coordinates.convertInWorldToChunk(blockInWorld, chunk); setChunk(getWorld().getChunk(chunk)); Vectors.release(chunk); } + + @Override + public ChunkLogic getChunk() { + return chunk; + } + + public void setChunk(ChunkLogic chunk) { + this.chunk = chunk; + } - public void setCoordsInChunk(Vec3i coords) { - getChunkCoords().set(coords.x, coords.y, coords.z); - Coordinates.getInWorld( - getChunkData().getPosition(), getChunkCoords(), - getCoords() - ); + public void init(Server server, Vec3i blockInWorld) { + setServer(server); + setCoordsInWorld(blockInWorld); } } diff --git a/src/main/java/ru/windcorp/progressia/server/world/MutableChunkTickContext.java b/src/main/java/ru/windcorp/progressia/server/world/MutableChunkTickContext.java deleted file mode 100644 index 775cc1e..0000000 --- a/src/main/java/ru/windcorp/progressia/server/world/MutableChunkTickContext.java +++ /dev/null @@ -1,66 +0,0 @@ -package ru.windcorp.progressia.server.world; - -import glm.vec._3.i.Vec3i; -import ru.windcorp.progressia.common.world.block.BlockFace; -import ru.windcorp.progressia.server.Server; - -public class MutableChunkTickContext implements ChunkTickContext { - - private double tickLength; - private Server server; - private WorldLogic world; - private ChunkLogic chunk; - - public MutableChunkTickContext() { - super(); - } - - public double getTickLength() { - return tickLength; - } - - public void setTickLength(double tickLength) { - this.tickLength = tickLength; - } - - @Override - public Server getServer() { - return server; - } - - public void setServer(Server server) { - this.server = server; - setWorld(server.getWorld()); - } - - @Override - public WorldLogic getWorld() { - return world; - } - - public void setWorld(WorldLogic world) { - this.world = world; - } - - @Override - public ChunkLogic getChunk() { - return chunk; - } - - public void setChunk(ChunkLogic chunk) { - this.chunk = chunk; - } - - @Override - public void requestBlockTick(Vec3i blockInWorld) { - // TODO implement - throw new UnsupportedOperationException("Not yet implemented"); - } - - @Override - public void requestTileTick(Vec3i blockInWorld, BlockFace face, int layer) { - // TODO implement - throw new UnsupportedOperationException("Not yet implemented"); - } - -} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/server/world/MutableTickContext.java b/src/main/java/ru/windcorp/progressia/server/world/MutableTickContext.java new file mode 100644 index 0000000..3476db5 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/MutableTickContext.java @@ -0,0 +1,43 @@ +package ru.windcorp.progressia.server.world; + +import ru.windcorp.progressia.server.Server; + +public class MutableTickContext implements TickContext { + + private double tickLength; + private Server server; + private WorldLogic world; + + public MutableTickContext() { + super(); + } + + public double getTickLength() { + return tickLength; + } + + public void setTickLength(double tickLength) { + this.tickLength = tickLength; + } + + @Override + public Server getServer() { + return server; + } + + public void setServer(Server server) { + this.server = server; + this.setTickLength(server.getTickLength()); + setWorld(server.getWorld()); + } + + @Override + public WorldLogic getWorld() { + return world; + } + + public void setWorld(WorldLogic world) { + this.world = world; + } + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/server/world/MutableTileTickContext.java b/src/main/java/ru/windcorp/progressia/server/world/MutableTileTickContext.java index 6770834..162659c 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/MutableTileTickContext.java +++ b/src/main/java/ru/windcorp/progressia/server/world/MutableTileTickContext.java @@ -1,60 +1,47 @@ package ru.windcorp.progressia.server.world; 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; +import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.world.tile.TileTickContext; public class MutableTileTickContext -extends MutableChunkTickContext +extends MutableTickContext implements TileTickContext { - private final Vec3i blockInWorld = new Vec3i(); - private final Vec3i blockInChunk = new Vec3i(); + private final Vec3i currentBlockInWorld = new Vec3i(); + private final Vec3i counterBlockInWorld = new Vec3i(); private BlockFace face; private int layer; @Override - public Vec3i getCoords() { - return this.blockInWorld; + public Vec3i getCurrentBlockInWorld() { + return this.currentBlockInWorld; } @Override - public Vec3i getChunkCoords() { - return this.blockInChunk; + public Vec3i getCounterBlockInWorld() { + return this.counterBlockInWorld; } - public void setCoordsInWorld(Vec3i coords) { - getCoords().set(coords.x, coords.y, coords.z); - Coordinates.convertInWorldToInChunk(getCoords(), getChunkCoords()); - - Vec3i chunk = Vectors.grab3i(); - Coordinates.convertInWorldToChunk(coords, chunk); - setChunk(getWorld().getChunk(chunk)); - Vectors.release(chunk); - } - - public void setCoordsInChunk(Vec3i coords) { - getChunkCoords().set(coords.x, coords.y, coords.z); - Coordinates.getInWorld( - getChunkData().getPosition(), getChunkCoords(), - getCoords() - ); + public void setCoordsInWorld(Vec3i currentBlockInWorld) { + getCurrentBlockInWorld().set(currentBlockInWorld.x, currentBlockInWorld.y, currentBlockInWorld.z); + getCounterBlockInWorld().set(currentBlockInWorld.x, currentBlockInWorld.y, currentBlockInWorld.z).add(getCurrentFace().getVector()); } @Override - public BlockFace getFace() { + public BlockFace getCurrentFace() { return face; } public void setFace(BlockFace face) { this.face = face; + setCoordsInWorld(getCurrentBlockInWorld()); } @Override - public int getLayer() { + public int getCurrentLayer() { return layer; } @@ -62,4 +49,11 @@ implements TileTickContext { this.layer = layer; } + public void init(Server server, Vec3i blockInWorld, BlockFace face, int layer) { + setServer(server); + setFace(face); + setCoordsInWorld(blockInWorld); + setLayer(layer); + } + } diff --git a/src/main/java/ru/windcorp/progressia/server/world/TickAndUpdateUtil.java b/src/main/java/ru/windcorp/progressia/server/world/TickAndUpdateUtil.java new file mode 100644 index 0000000..6270361 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/TickAndUpdateUtil.java @@ -0,0 +1,163 @@ +package ru.windcorp.progressia.server.world; + +import java.util.List; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.LowOverheadCache; +import ru.windcorp.progressia.common.util.crash.CrashReports; +import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.block.BlockTickContext; +import ru.windcorp.progressia.server.world.block.TickableBlock; +import ru.windcorp.progressia.server.world.block.UpdateableBlock; +import ru.windcorp.progressia.server.world.tile.TickableTile; +import ru.windcorp.progressia.server.world.tile.TileLogic; +import ru.windcorp.progressia.server.world.tile.TileTickContext; +import ru.windcorp.progressia.server.world.tile.UpdateableTile; + +public class TickAndUpdateUtil { + + private static final LowOverheadCache JAVAPONY_S_ULTIMATE_BLOCK_TICK_CONTEXT_SUPPLY = + new LowOverheadCache<>(MutableBlockTickContext::new); + + private static final LowOverheadCache JAVAPONY_S_ULTIMATE_TILE_TICK_CONTEXT_SUPPLY = + new LowOverheadCache<>(MutableTileTickContext::new); + + public static void tickBlock(TickableBlock block, BlockTickContext context) { + try { + block.tick(context); + } catch (Exception e) { + CrashReports.report(e, "Could not tick block {}", block); + } + } + + public static void tickBlock(WorldLogic world, Vec3i blockInWorld) { + BlockLogic block = world.getBlock(blockInWorld); + if (!(block instanceof TickableBlock)) return; // also checks nulls + + BlockTickContext tickContext = grabBlockTickContext(world.getServer(), blockInWorld); + tickBlock((TickableBlock) block, tickContext); + releaseTickContext(tickContext); + } + + public static void tickTile(TickableTile tile, TileTickContext context) { + try { + tile.tick(context); + } catch (Exception e) { + CrashReports.report(e, "Could not tick tile {}", tile); + } + } + + public static void tickTile(WorldLogic world, Vec3i blockInWorld, BlockFace face, int layer) { + TileLogic tile = world.getTile(blockInWorld, face, layer); + if (!(tile instanceof TickableTile)) return; + + TileTickContext tickContext = grabTileTickContext(world.getServer(), blockInWorld, face, layer); + tickTile((TickableTile) tile, tickContext); + releaseTickContext(tickContext); + } + + public static void tickTiles(WorldLogic world, Vec3i blockInWorld, BlockFace face) { + List tiles = world.getTilesOrNull(blockInWorld, face); + if (tiles == null || tiles.isEmpty()) return; + + MutableTileTickContext tickContext = JAVAPONY_S_ULTIMATE_TILE_TICK_CONTEXT_SUPPLY.grab(); + + for (int layer = 0; layer < tiles.size(); ++layer) { + TileLogic tile = tiles.get(layer); + if (!(tile instanceof TickableTile)) return; + + tickContext.init(world.getServer(), blockInWorld, face, layer); + tickTile((TickableTile) tile, tickContext); + } + + JAVAPONY_S_ULTIMATE_TILE_TICK_CONTEXT_SUPPLY.release(tickContext); + } + + public static void updateBlock(UpdateableBlock block, BlockTickContext context) { + try { + block.update(context); + } catch (Exception e) { + CrashReports.report(e, "Could not update block {}", block); + } + } + + public static void updateBlock(WorldLogic world, Vec3i blockInWorld) { + BlockLogic block = world.getBlock(blockInWorld); + if (!(block instanceof UpdateableBlock)) return; // also checks nulls + + BlockTickContext tickContext = grabBlockTickContext(world.getServer(), blockInWorld); + updateBlock((UpdateableBlock) block, tickContext); + releaseTickContext(tickContext); + } + + public static void updateTile(UpdateableTile tile, TileTickContext context) { + try { + tile.update(context); + } catch (Exception e) { + CrashReports.report(e, "Could not update tile {}", tile); + } + } + + public static void updateTile(WorldLogic world, Vec3i blockInWorld, BlockFace face, int layer) { + TileLogic tile = world.getTile(blockInWorld, face, layer); + if (!(tile instanceof UpdateableTile)) return; + + TileTickContext tickContext = grabTileTickContext(world.getServer(), blockInWorld, face, layer); + updateTile((UpdateableTile) tile, tickContext); + releaseTickContext(tickContext); + } + + public static void updateTiles(WorldLogic world, Vec3i blockInWorld, BlockFace face) { + List tiles = world.getTilesOrNull(blockInWorld, face); + if (tiles == null || tiles.isEmpty()) return; + + MutableTileTickContext tickContext = JAVAPONY_S_ULTIMATE_TILE_TICK_CONTEXT_SUPPLY.grab(); + + for (int layer = 0; layer < tiles.size(); ++layer) { + TileLogic tile = tiles.get(layer); + if (!(tile instanceof UpdateableTile)) return; + + tickContext.init(world.getServer(), blockInWorld, face, layer); + updateTile((UpdateableTile) tile, tickContext); + } + + JAVAPONY_S_ULTIMATE_TILE_TICK_CONTEXT_SUPPLY.release(tickContext); + } + + public static BlockTickContext grabBlockTickContext( + Server server, + Vec3i blockInWorld + ) { + MutableBlockTickContext result = JAVAPONY_S_ULTIMATE_BLOCK_TICK_CONTEXT_SUPPLY.grab(); + result.init(server, blockInWorld); + return result; + } + + public static TileTickContext grabTileTickContext( + Server server, + Vec3i blockInWorld, + BlockFace face, + int layer + ) { + MutableTileTickContext result = JAVAPONY_S_ULTIMATE_TILE_TICK_CONTEXT_SUPPLY.grab(); + result.init(server, blockInWorld, face, layer); + return result; + } + + public static void releaseTickContext(BlockTickContext context) { + JAVAPONY_S_ULTIMATE_BLOCK_TICK_CONTEXT_SUPPLY.release( + (MutableBlockTickContext) context + ); + } + + public static void releaseTickContext(TileTickContext context) { + JAVAPONY_S_ULTIMATE_TILE_TICK_CONTEXT_SUPPLY.release( + (MutableTileTickContext) context + ); + } + + private TickAndUpdateUtil() {} + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/TickContext.java b/src/main/java/ru/windcorp/progressia/server/world/TickContext.java index 73d43e0..de41d98 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/TickContext.java +++ b/src/main/java/ru/windcorp/progressia/server/world/TickContext.java @@ -1,9 +1,8 @@ package ru.windcorp.progressia.server.world; -import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.WorldData; -import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.tasks.WorldAccessor; public interface TickContext { @@ -15,12 +14,12 @@ public interface TickContext { return getServer().getWorld(); } + default WorldAccessor getAccessor() { + return getServer().getWorldAccessor(); + } + default WorldData getWorldData() { return getWorld().getData(); } - - void requestBlockTick(Vec3i blockInWorld); - - void requestTileTick(Vec3i blockInWorld, BlockFace face, int layer); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/Ticker.java b/src/main/java/ru/windcorp/progressia/server/world/Ticker.java deleted file mode 100644 index 4aa1bdc..0000000 --- a/src/main/java/ru/windcorp/progressia/server/world/Ticker.java +++ /dev/null @@ -1,98 +0,0 @@ -package ru.windcorp.progressia.server.world; - -import ru.windcorp.progressia.server.Server; -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.tile.TickableTile; - -public class Ticker implements Runnable { - - private final ImplementedChangeTracker tracker = - new ImplementedChangeTracker(); - - private final MutableBlockTickContext blockTickContext = - new MutableBlockTickContext(); - - private final MutableTileTickContext tileTickContext = - new MutableTileTickContext(); - - private final Server server; - - public Ticker(Server server) { - this.server = server; - } - - @Override - public void run() { - this.blockTickContext.setTickLength(1000 / 20); - server.getWorld().getChunks().forEach(chunk -> { - tickChunk(chunk); - }); - } - - private void tickChunk(ChunkLogic chunk) { - MutableBlockTickContext blockContext = this.blockTickContext; - MutableTileTickContext tileContext = this.tileTickContext; - - blockContext.setServer(server); - tileContext.setServer(server); - - blockContext.setChunk(chunk); - tileContext.setChunk(chunk); - - tickRegularTickers(chunk, blockContext, tileContext); - tickRandomBlocks(chunk, blockContext, tileContext); - - tickEntities(chunk, blockContext); - - flushChanges(chunk); - } - - private void tickEntities( - ChunkLogic chunk, - MutableChunkTickContext tickContext - ) { - // TODO this is ugly - - chunk.getData().getEntities().forEach(entity -> { - EntityLogic logic = EntityLogicRegistry.getInstance().get( - entity.getId() - ); - - logic.tick(entity, tickContext, tracker); - }); - } - - private void tickRegularTickers( - ChunkLogic chunk, - MutableBlockTickContext blockContext, - MutableTileTickContext tileContext - ) { - chunk.forEachTickingBlock((blockInChunk, block) -> { - blockContext.setCoordsInChunk(blockInChunk); - ((TickableBlock) block).tick(blockContext, tracker); - }); - - chunk.forEachTickingTile((locInChunk, tile) -> { - tileContext.setCoordsInChunk(locInChunk.pos); - tileContext.setFace(locInChunk.face); - tileContext.setLayer(locInChunk.layer); - - ((TickableTile) tile).tick(tileContext, tracker); - }); - } - - private void tickRandomBlocks( - ChunkLogic chunk, - MutableBlockTickContext blockContext, - MutableTileTickContext tileContext - ) { - // TODO implement - } - - private void flushChanges(ChunkLogic chunk) { - this.tracker.applyChanges(server); - } - -} diff --git a/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java b/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java index 10e0db8..0e6c61a 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java @@ -2,24 +2,34 @@ 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.util.Vectors; import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.ChunkDataListener; +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.BlockData; +import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.tile.TileData; +import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.tile.TileLogic; public class WorldLogic { private final WorldData data; + private final Server server; private final Map chunks = new HashMap<>(); - public WorldLogic(WorldData data) { + public WorldLogic(WorldData data, Server server) { this.data = data; + this.server = server; data.addListener(new WorldDataListener() { @Override @@ -32,6 +42,33 @@ public class WorldLogic { chunks.remove(chunk); } }); + + data.addListener(ChunkDataListeners.createAdder(new ChunkDataListener() { + @Override + public void onChunkBlockChanged( + ChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current + ) { + Vec3i blockInWorld = Vectors.grab3i(); + Coordinates.getInWorld(chunk.getPosition(), blockInChunk, blockInWorld); + getServer().getWorldAccessor().triggerUpdates(blockInWorld); + Vectors.release(blockInWorld); + } + + @Override + public void onChunkTilesChanged( + ChunkData chunk, Vec3i blockInChunk, BlockFace face, TileData tile, + boolean wasAdded + ) { + Vec3i blockInWorld = Vectors.grab3i(); + Coordinates.getInWorld(chunk.getPosition(), blockInChunk, blockInWorld); + getServer().getWorldAccessor().triggerUpdates(blockInWorld, face); + Vectors.release(blockInWorld); + } + })); + } + + public Server getServer() { + return server; } public WorldData getData() { @@ -61,10 +98,42 @@ public class WorldLogic { Vec3i blockInChunk = Vectors.grab3i(); Coordinates.convertInWorldToInChunk(blockInWorld, blockInChunk); BlockLogic result = chunk.getBlock(blockInChunk); + Vectors.release(blockInChunk); + return result; + } + + public List getTiles(Vec3i blockInWorld, BlockFace face) { + return getTilesImpl(blockInWorld, face, true); + } + + public List getTilesOrNull(Vec3i blockInWorld, BlockFace face) { + return getTilesImpl(blockInWorld, face, false); + } + + private List getTilesImpl(Vec3i blockInWorld, BlockFace face, boolean createIfMissing) { + ChunkLogic chunk = getChunkByBlock(blockInWorld); + if (chunk == null) return null; + + Vec3i blockInChunk = Vectors.grab3i(); + Coordinates.convertInWorldToInChunk(blockInWorld, blockInChunk); + + List result = + createIfMissing + ? chunk.getTiles(blockInChunk, face) + : chunk.getTilesOrNull(blockInChunk, face); + + Vectors.release(blockInChunk); return result; } + public TileLogic getTile(Vec3i blockInWorld, BlockFace face, int layer) { + List tiles = getTilesOrNull(blockInWorld, face); + if (tiles == null || tiles.size() <= layer) return null; + + return tiles.get(layer); + } + public Collection getChunks() { return chunks.values(); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/block/BlockLogic.java b/src/main/java/ru/windcorp/progressia/server/world/block/BlockLogic.java index 5c9645a..045e707 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/block/BlockLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/block/BlockLogic.java @@ -1,11 +1,28 @@ package ru.windcorp.progressia.server.world.block; import ru.windcorp.progressia.common.util.namespaces.Namespaced; +import ru.windcorp.progressia.common.world.block.BlockFace; public class BlockLogic extends Namespaced { public BlockLogic(String id) { super(id); } + + public boolean isSolid(BlockTickContext context, BlockFace face) { + return isSolid(face); + } + + public boolean isSolid(BlockFace face) { + return true; + } + + public boolean isTransparent(BlockTickContext context) { + return isTransparent(); + } + + public boolean isTransparent() { + return false; + } } diff --git a/src/main/java/ru/windcorp/progressia/server/world/block/BlockTickContext.java b/src/main/java/ru/windcorp/progressia/server/world/block/BlockTickContext.java index ded8e92..128cd1c 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/block/BlockTickContext.java +++ b/src/main/java/ru/windcorp/progressia/server/world/block/BlockTickContext.java @@ -10,20 +10,14 @@ public interface BlockTickContext extends ChunkTickContext { * Returns the current world coordinates. * @return the world coordinates of the block being ticked */ - Vec3i getCoords(); - - /** - * Returns the current chunk coordinates. - * @return the chunk coordinates of the block being ticked - */ - Vec3i getChunkCoords(); + Vec3i getBlockInWorld(); default BlockLogic getBlock() { - return getChunk().getBlock(getChunkCoords()); + return getWorld().getBlock(getBlockInWorld()); } default BlockData getBlockData() { - return getChunkData().getBlock(getChunkCoords()); + return getWorldData().getBlock(getBlockInWorld()); } } diff --git a/src/main/java/ru/windcorp/progressia/server/world/block/ForwardingBlockTickContext.java b/src/main/java/ru/windcorp/progressia/server/world/block/ForwardingBlockTickContext.java index 03be3be..ae289cb 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/block/ForwardingBlockTickContext.java +++ b/src/main/java/ru/windcorp/progressia/server/world/block/ForwardingBlockTickContext.java @@ -1,13 +1,8 @@ package ru.windcorp.progressia.server.world.block; import glm.vec._3.i.Vec3i; -import ru.windcorp.progressia.common.world.ChunkData; -import ru.windcorp.progressia.common.world.WorldData; -import ru.windcorp.progressia.common.world.block.BlockData; -import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.world.ChunkLogic; -import ru.windcorp.progressia.server.world.WorldLogic; public class ForwardingBlockTickContext implements BlockTickContext { @@ -29,11 +24,6 @@ public class ForwardingBlockTickContext implements BlockTickContext { public ChunkLogic getChunk() { return parent.getChunk(); } - - @Override - public ChunkData getChunkData() { - return parent.getChunkData(); - } @Override public double getTickLength() { @@ -46,43 +36,8 @@ public class ForwardingBlockTickContext implements BlockTickContext { } @Override - public Vec3i getCoords() { - return parent.getCoords(); - } - - @Override - public WorldLogic getWorld() { - return parent.getWorld(); - } - - @Override - public WorldData getWorldData() { - return parent.getWorldData(); - } - - @Override - public Vec3i getChunkCoords() { - return parent.getChunkCoords(); - } - - @Override - public void requestBlockTick(Vec3i blockInWorld) { - parent.requestBlockTick(blockInWorld); - } - - @Override - public void requestTileTick(Vec3i blockInWorld, BlockFace face, int layer) { - parent.requestTileTick(blockInWorld, face, layer); - } - - @Override - public BlockLogic getBlock() { - return parent.getBlock(); - } - - @Override - public BlockData getBlockData() { - return parent.getBlockData(); + public Vec3i getBlockInWorld() { + return parent.getBlockInWorld(); } } diff --git a/src/main/java/ru/windcorp/progressia/server/world/block/TickableBlock.java b/src/main/java/ru/windcorp/progressia/server/world/block/TickableBlock.java index 4bdfceb..603aa3b 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/block/TickableBlock.java +++ b/src/main/java/ru/windcorp/progressia/server/world/block/TickableBlock.java @@ -1,10 +1,8 @@ package ru.windcorp.progressia.server.world.block; -import ru.windcorp.progressia.server.world.Changer; - public interface TickableBlock { - void tick(BlockTickContext context, Changer changer); + void tick(BlockTickContext context); default boolean doesTickRegularly(BlockTickContext context) { return false; diff --git a/src/main/java/ru/windcorp/progressia/server/world/block/UpdateableBlock.java b/src/main/java/ru/windcorp/progressia/server/world/block/UpdateableBlock.java index 406f4c9..c500515 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/block/UpdateableBlock.java +++ b/src/main/java/ru/windcorp/progressia/server/world/block/UpdateableBlock.java @@ -1,9 +1,11 @@ package ru.windcorp.progressia.server.world.block; -import ru.windcorp.progressia.server.world.Changer; +import org.apache.logging.log4j.LogManager; public interface UpdateableBlock { - void update(BlockTickContext context, Changer changer); + default void update(BlockTickContext context) { + LogManager.getLogger().info("Updating block {} @ ({}; {}; {})", context.getBlock(), context.getBlockInWorld().x, context.getBlockInWorld().y, context.getBlockInWorld().z); + } } diff --git a/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogic.java b/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogic.java index f678e25..3dc67f7 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/entity/EntityLogic.java @@ -2,7 +2,6 @@ package ru.windcorp.progressia.server.world.entity; import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.world.entity.EntityData; -import ru.windcorp.progressia.server.world.Changer; import ru.windcorp.progressia.server.world.TickContext; public class EntityLogic extends Namespaced { @@ -11,7 +10,7 @@ public class EntityLogic extends Namespaced { super(id); } - public void tick(EntityData entity, TickContext context, Changer changer) { + public void tick(EntityData entity, TickContext context) { entity.incrementAge(context.getTickLength()); } diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/AddOrRemoveTile.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/AddOrRemoveTile.java new file mode 100644 index 0000000..fe0da62 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/AddOrRemoveTile.java @@ -0,0 +1,66 @@ +package ru.windcorp.progressia.server.world.tasks; + +import java.util.List; +import java.util.function.Consumer; + +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.WorldData; +import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.tile.TileData; + +class AddOrRemoveTile extends CachedWorldChange { + + private final Vec3i blockInWorld = new Vec3i(); + private BlockFace face; + private TileData tile; + + private boolean shouldAdd; + + public AddOrRemoveTile(Consumer disposer) { + super(disposer, "Core:AddOrRemoveTile"); + } + + public void initialize( + Vec3i position, BlockFace face, + TileData tile, + boolean shouldAdd + ) { + 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; + this.shouldAdd = shouldAdd; + } + + @Override + protected void affectCommon(WorldData world) { + Vec3i blockInChunk = Vectors.grab3i(); + Coordinates.convertInWorldToInChunk(blockInWorld, blockInChunk); + + List tiles = world.getChunkByBlock(blockInWorld).getTiles(blockInChunk, face); + + if (shouldAdd) { + tiles.add(tile); + } else { + tiles.remove(tile); + } + + Vectors.release(blockInChunk); + } + + @Override + public void getRelevantChunk(Vec3i output) { + Coordinates.convertInWorldToChunk(blockInWorld, output); + } + + @Override + public void dispose() { + super.dispose(); + this.tile = null; + } + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/BlockTriggeredUpdate.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/BlockTriggeredUpdate.java new file mode 100644 index 0000000..2503e2a --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/BlockTriggeredUpdate.java @@ -0,0 +1,47 @@ +package ru.windcorp.progressia.server.world.tasks; + +import java.util.function.Consumer; + +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; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.TickAndUpdateUtil; +import ru.windcorp.progressia.server.world.WorldLogic; + +class BlockTriggeredUpdate extends CachedEvaluation { + + private final Vec3i blockInWorld = new Vec3i(); + + public BlockTriggeredUpdate(Consumer disposer) { + super(disposer); + } + + @Override + public void evaluate(Server server) { + Vec3i cursor = Vectors.grab3i(); + cursor.set(blockInWorld.x, blockInWorld.y, blockInWorld.z); + + WorldLogic world = server.getWorld(); + + for (BlockFace face : BlockFace.getFaces()) { + TickAndUpdateUtil.updateTiles(world, cursor, face); + cursor.add(face.getVector()); + TickAndUpdateUtil.updateBlock(world, cursor); + cursor.sub(face.getVector()); + } + + Vectors.release(cursor); + } + + public void init(Vec3i blockInWorld) { + this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z); + } + + @Override + public void getRelevantChunk(Vec3i output) { + Coordinates.convertInWorldToChunk(blockInWorld, output); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/CachedChange.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/CachedChange.java new file mode 100644 index 0000000..081ce8e --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/CachedChange.java @@ -0,0 +1,20 @@ +package ru.windcorp.progressia.server.world.tasks; + +import java.util.function.Consumer; + +import ru.windcorp.progressia.server.world.ticking.Change; + +public abstract class CachedChange extends Change { + + private final Consumer disposer; + + public CachedChange(Consumer disposer) { + this.disposer = disposer; + } + + @Override + public void dispose() { + disposer.accept(this); + } + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/CachedEvaluation.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/CachedEvaluation.java new file mode 100644 index 0000000..d10a203 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/CachedEvaluation.java @@ -0,0 +1,20 @@ +package ru.windcorp.progressia.server.world.tasks; + +import java.util.function.Consumer; + +import ru.windcorp.progressia.server.world.ticking.Evaluation; + +public abstract class CachedEvaluation extends Evaluation { + + private final Consumer disposer; + + public CachedEvaluation(Consumer disposer) { + this.disposer = disposer; + } + + @Override + public void dispose() { + disposer.accept(this); + } + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/CachedWorldChange.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/CachedWorldChange.java new file mode 100644 index 0000000..be587f8 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/CachedWorldChange.java @@ -0,0 +1,36 @@ +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 ru.windcorp.progressia.server.Server; + +public abstract class CachedWorldChange extends CachedChange { + + private final PacketWorldChange packet; + + public CachedWorldChange(Consumer disposer, String packetId) { + super(disposer); + + this.packet = new PacketWorldChange(packetId) { + @Override + public void apply(WorldData world) { + affectCommon(world); + } + }; + } + + @Override + public void affect(Server server) { + affectCommon(server.getWorld().getData()); + server.getClientManager().broadcastGamePacket(packet); + } + + /** + * Invoked by both Change and Packet. + * @param world the world to affect + */ + protected abstract void affectCommon(WorldData world); + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/ChangeEntity.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/ChangeEntity.java new file mode 100644 index 0000000..f671914 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/ChangeEntity.java @@ -0,0 +1,73 @@ +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; + +class ChangeEntity extends CachedChange { + + private EntityData entity; + private StateChange change; + + private final PacketEntityChange packet = new PacketEntityChange(); + + public ChangeEntity(Consumer disposer) { + super(disposer); + } + + public void set(T entity, StateChange change) { + if (this.entity != null) + throw new IllegalStateException("Entity is not null. Current: " + this.entity + "; requested: " + entity); + + if (this.change != null) + throw new IllegalStateException("Change is not null. Current: " + this.change + "; requested: " + change); + + 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) change).change(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); + } + + @Override + public void getRelevantChunk(Vec3i output) { + // Do nothing + } + + @Override + public boolean isThreadSensitive() { + return false; + } + + @Override + public void dispose() { + super.dispose(); + this.entity = null; + this.change = null; + } + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/SetBlock.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/SetBlock.java new file mode 100644 index 0000000..b8a9992 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/SetBlock.java @@ -0,0 +1,49 @@ +package ru.windcorp.progressia.server.world.tasks; + +import java.util.function.Consumer; + +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.WorldData; +import ru.windcorp.progressia.common.world.block.BlockData; + +class SetBlock extends CachedWorldChange { + + private final Vec3i blockInWorld = new Vec3i(); + private BlockData block; + + public SetBlock(Consumer 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) { + Vec3i blockInChunk = Vectors.grab3i(); + Coordinates.convertInWorldToInChunk(blockInWorld, blockInChunk); + + world.getChunkByBlock(blockInWorld).setBlock(blockInChunk, block, true); + + Vectors.release(blockInChunk); + } + + @Override + public void getRelevantChunk(Vec3i output) { + Coordinates.convertInWorldToChunk(blockInWorld, output); + } + + @Override + public void dispose() { + super.dispose(); + this.block = null; + } + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/StateChange.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/StateChange.java new file mode 100644 index 0000000..826096b --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/StateChange.java @@ -0,0 +1,6 @@ +package ru.windcorp.progressia.server.world.tasks; + +@FunctionalInterface +public interface StateChange { + void change(T object); +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/TileTriggeredUpdate.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/TileTriggeredUpdate.java new file mode 100644 index 0000000..e87d9a1 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/TileTriggeredUpdate.java @@ -0,0 +1,47 @@ +package ru.windcorp.progressia.server.world.tasks; + +import java.util.function.Consumer; + +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; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.TickAndUpdateUtil; +import ru.windcorp.progressia.server.world.WorldLogic; + +class TileTriggeredUpdate extends CachedEvaluation { + + private final Vec3i blockInWorld = new Vec3i(); + private BlockFace face = null; + + public TileTriggeredUpdate(Consumer disposer) { + super(disposer); + } + + @Override + public void evaluate(Server server) { + Vec3i cursor = Vectors.grab3i(); + cursor.set(blockInWorld.x, blockInWorld.y, blockInWorld.z); + + WorldLogic world = server.getWorld(); + + TickAndUpdateUtil.tickTiles(world, cursor, face); // Tick facemates (also self) + TickAndUpdateUtil.tickBlock(world, cursor); // Tick block on one side + cursor.add(face.getVector()); + TickAndUpdateUtil.tickBlock(world, cursor); // Tick block on the other side + + Vectors.release(cursor); + } + + public void init(Vec3i blockInWorld, BlockFace face) { + this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z); + this.face = face; + } + + @Override + public void getRelevantChunk(Vec3i output) { + Coordinates.convertInWorldToChunk(blockInWorld, output); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/WorldAccessor.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/WorldAccessor.java new file mode 100644 index 0000000..9f24aff --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/WorldAccessor.java @@ -0,0 +1,89 @@ +package ru.windcorp.progressia.server.world.tasks; + +import java.util.function.Consumer; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.MultiLOC; +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.tile.TileData; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.ticking.TickerTask; + +public class WorldAccessor { + + private final MultiLOC cache; + { + MultiLOC mloc = new MultiLOC(); + Consumer disposer = mloc::release; + + cache = mloc + .addClass(SetBlock.class, () -> new SetBlock(disposer)) + .addClass(AddOrRemoveTile.class, () -> new AddOrRemoveTile(disposer)) + .addClass(ChangeEntity.class, () -> new ChangeEntity(disposer)) + + .addClass(BlockTriggeredUpdate.class, () -> new BlockTriggeredUpdate(disposer)) + .addClass(TileTriggeredUpdate.class, () -> new TileTriggeredUpdate(disposer)); + } + + private final Server server; + + public WorldAccessor(Server server) { + this.server = server; + } + + public void setBlock(Vec3i blockInWorld, BlockData block) { + SetBlock change = cache.grab(SetBlock.class); + change.initialize(blockInWorld, block); + server.requestChange(change); + } + + public void addTile(Vec3i blockInWorld, BlockFace face, TileData tile) { + AddOrRemoveTile change = cache.grab(AddOrRemoveTile.class); + change.initialize(blockInWorld, face, tile, true); + server.requestChange(change); + } + + public void removeTile(Vec3i blockInWorld, BlockFace face, TileData tile) { + AddOrRemoveTile change = cache.grab(AddOrRemoveTile.class); + change.initialize(blockInWorld, face, tile, false); + server.requestChange(change); + } + + public void changeEntity( + T entity, StateChange stateChange + ) { + ChangeEntity change = cache.grab(ChangeEntity.class); + change.set(entity, stateChange); + server.requestChange(change); + } + + public void tickBlock(Vec3i blockInWorld) { + // TODO + } + + /** + * When a block is the trigger + * @param blockInWorld + */ + // TODO rename to something meaningful + public void triggerUpdates(Vec3i blockInWorld) { + BlockTriggeredUpdate evaluation = cache.grab(BlockTriggeredUpdate.class); + evaluation.init(blockInWorld); + server.requestEvaluation(evaluation); + } + + /** + * When a tile is the trigger + * @param blockInWorld + * @param face + */ + // TODO rename to something meaningful + public void triggerUpdates(Vec3i blockInWorld, BlockFace face) { + TileTriggeredUpdate evaluation = cache.grab(TileTriggeredUpdate.class); + evaluation.init(blockInWorld, face); + server.requestEvaluation(evaluation); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/ticking/Change.java b/src/main/java/ru/windcorp/progressia/server/world/ticking/Change.java new file mode 100644 index 0000000..408ed3a --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/ticking/Change.java @@ -0,0 +1,27 @@ +package ru.windcorp.progressia.server.world.ticking; + +import ru.windcorp.progressia.server.Server; + +/** + * A {@link TickerTask} that aims to perform a predetermined set of changes on the world. + * @author javapony + */ +public abstract class Change extends TickerTask { + + /** + * Performs the changes on the provided server instance. + *

+ * This method will be executed when the world is in an inconsistent state and may not be queried, + * only changed. Therefore, all necessary inspection must be performed before this method is invoked, + * typically by an {@link Evaluation}. Failure to abide by this contract may lead to race conditions + * and/or devil invasions. + * @param server the {@link Server} instance to affect + */ + public abstract void affect(Server server); + + @Override + void run(Server server) { + affect(server); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/ticking/DevilInvasionException.java b/src/main/java/ru/windcorp/progressia/server/world/ticking/DevilInvasionException.java new file mode 100644 index 0000000..64721af --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/ticking/DevilInvasionException.java @@ -0,0 +1,12 @@ +package ru.windcorp.progressia.server.world.ticking; + +public class DevilInvasionException extends RuntimeException { + + private static final long serialVersionUID = "devil666satan".hashCode(); + + private DevilInvasionException() { + // You don't choose when an invasion occurs. + // _They_ do. + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/ticking/Evaluation.java b/src/main/java/ru/windcorp/progressia/server/world/ticking/Evaluation.java new file mode 100644 index 0000000..a6cee9b --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/ticking/Evaluation.java @@ -0,0 +1,29 @@ +package ru.windcorp.progressia.server.world.ticking; + +import ru.windcorp.progressia.server.Server; + +/** + * A {@link TickerTask} that needs to access the world for analysis. + * @author javapony + */ +public abstract class Evaluation extends TickerTask { + + /** + * Performs the analysis of the provided server instance. + *

+ * This method will be executed when the world is in an consistent state + * and may be queried for meaningful information. However, other + * evaluations may be happening concurrently, so any world modification + * is prohibited. Evaluations are expected to request {@link Change}s + * to interact with the world. Failure to abide by this contract may + * lead to race conditions and/or devil invasions. + * @param server the server instance to inspect + */ + public abstract void evaluate(Server server); + + @Override + void run(Server server) { + evaluate(server); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/ticking/Ticker.java b/src/main/java/ru/windcorp/progressia/server/world/ticking/Ticker.java new file mode 100644 index 0000000..1ae5111 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/ticking/Ticker.java @@ -0,0 +1,164 @@ +package ru.windcorp.progressia.server.world.ticking; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import ru.windcorp.progressia.common.util.crash.CrashReports; +import ru.windcorp.progressia.server.Server; + +class Ticker { + + private final String name; + private final int id; + + private Thread thread = null; + private final TickerCoordinator coordinator; + + private volatile boolean shouldRun = true; + + // Expected to implement RandomAccess + private final List tasks = new ArrayList<>(TickerCoordinator.INITIAL_QUEUE_SIZE); + + private final Logger logger; + + public Ticker(String name, int id, TickerCoordinator coordinator) { + this.name = Objects.requireNonNull(name, "name"); + this.id = id; + this.coordinator = Objects.requireNonNull(coordinator, "coordinator"); + + this.logger = LogManager.getLogger(this.name); + } + + public synchronized void start() { + if (thread != null) + throw new IllegalStateException("Ticker already started in thread " + thread); + + thread = new Thread(this::run, this.name); + logger.debug("Starting"); + thread.start(); + } + + public String getName() { + return name; + } + + public int getId() { + return id; + } + + public Thread getThread() { + return thread; + } + + public TickerCoordinator getCoordinator() { + return coordinator; + } + + public synchronized void stop() { + if (thread == null) + return; + + shouldRun = false; + thread.interrupt(); + + logger.debug("Stopping"); + } + + public synchronized void requestWork(Collection tasks) { + int currentTaskCount = this.tasks.size(); + if (currentTaskCount != 0) { + throw new IllegalStateException("Ticker already has " + currentTaskCount + " tasks"); + } + + this.tasks.addAll(Objects.requireNonNull(tasks, "tasks")); + this.notifyAll(); + + logger.debug("Work {} requested", tasks.size()); + } + + private void run() { + try { + logger.debug("Started"); + + while (!Thread.interrupted()) { + boolean shouldStop = sleep(); + if (shouldStop) break; + work(); + } + + logger.debug("Stopped"); + + // Do not release Thread reference so start() still throws ISE + } catch (Exception e) { + getCoordinator().crash(e, this.name); + } + } + + private synchronized boolean sleep() { + logger.debug("Entering sleep"); + + try { + while (true) { + + if (!shouldRun) { + logger.debug("Exiting sleep: received stop request"); + return true; + } + + int taskCount = tasks.size(); + if (taskCount > 0) { + logger.debug("Exiting sleep: received {} tasks", taskCount); + return false; + } + + logger.debug("Waiting"); + this.wait(); + + } + } catch (InterruptedException e) { + logger.debug("Exiting sleep: interrupted"); + return true; + } + } + + private void work() { + logger.debug("Starting work"); + + int tasksCompleted = runTasks(); + resetState(); + + logger.debug("Work complete; run {} tasks", tasksCompleted); + } + + private int runTasks() { + int tasksCompleted = 0; + + Server srv = getCoordinator().getServer(); + + for (int i = 0; i < tasks.size(); ++i) { + TickerTask task = tasks.get(i); + + assert task != null : "Encountered null task"; + + try { + task.run(srv); + } catch (Exception e) { + CrashReports.report(e, "Could not run {} task {}", task.getClass().getSimpleName(), task); + } + + tasksCompleted++; + } + + return tasksCompleted; + } + + private void resetState() { + tasks.clear(); + getCoordinator().reportWorkComplete(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/ticking/TickerCoordinator.java b/src/main/java/ru/windcorp/progressia/server/world/ticking/TickerCoordinator.java new file mode 100644 index 0000000..074f69b --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/ticking/TickerCoordinator.java @@ -0,0 +1,252 @@ +package ru.windcorp.progressia.server.world.ticking; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableList; + +import ru.windcorp.progressia.common.Units; +import ru.windcorp.progressia.common.util.crash.CrashReports; +import ru.windcorp.progressia.server.Server; + +/** + * Central control point for serverside ticking. This class provides an interface to + * interact with Tickers. + * @author javapony + */ +public class TickerCoordinator { + + static final int INITIAL_QUEUE_SIZE = 1024; + + private final Server server; + + // Synchronized manually + private final Collection pendingChanges = new ArrayList<>(INITIAL_QUEUE_SIZE); + // Synchronized manually + private final Collection pendingEvaluations = new ArrayList<>(INITIAL_QUEUE_SIZE); + + /** + * A cached ArrayList used to transfer tasks from Coordinator to Tickers. + * This list must be empty when not in {@link #startPassStage(Collection, String)}. + */ + private final Collection cachedTransferList = new ArrayList<>(INITIAL_QUEUE_SIZE); + + /** + * All tasks that must be {@linkplain TickerTask#dispose() disposed of} at the end of the current + * tick. This list must be empty when not in {@link #runPassStage(Collection, String)}. + */ + private final Collection toDispose = new ArrayList<>(INITIAL_QUEUE_SIZE); + + private final Collection tickers; + private final Collection threads; + + private final AtomicInteger workingTickers = new AtomicInteger(); + + private final Logger logger = LogManager.getLogger("Ticker Coordinator"); + + public TickerCoordinator(Server server, int tickers) { + this.server = Objects.requireNonNull(server, "server"); + + Collection tickerCollection = new ArrayList<>(); + + for (int i = 0; i < tickers; ++i) { + tickerCollection.add(new Ticker("Ticker " + i, i, this)); + } + + this.tickers = ImmutableList.copyOf(tickerCollection); + this.threads = Collections2.transform(this.tickers, Ticker::getThread); // Immutable because it is a view + } + + /* + * Public API + */ + + public synchronized void start() { + logger.debug("Starting tickers"); + tickers.forEach(Ticker::start); + logger.debug("Tickers started"); + } + + public synchronized void stop() { + logger.debug("Stopping tickers"); + tickers.forEach(Ticker::stop); + logger.debug("Tickers requested to stop"); + } + + public synchronized void requestChange(Change change) { + pendingChanges.add(change); + } + + public synchronized void requestEvaluation(Evaluation evaluation) { + pendingEvaluations.add(evaluation); + } + + public Server getServer() { + return server; + } + + public Collection getThreads() { + return this.threads; + } + + public double getTickLength() { + // TODO implement + return Units.SECONDS / 20; + } + + /* + * runOneTick & Friends + */ + + public void runOneTick() { + try { + + int passes = 0; + + logger.debug("Beginning tick"); + + while (hasPending()) { + logger.debug("Starting pass"); + runOnePass(); + logger.debug("Pass complete"); + passes++; + } + + logger.debug("Tick complete; run {} passes", passes); + + } catch (InterruptedException e) { + // Exit silently + + // ...or almost silently + logger.debug("Tick interrupted. WTF?"); + } catch (Exception e) { + crash(e, "Coordinator"); + } + } + + private boolean hasPending() { + // Race condition? + return !(pendingChanges.isEmpty() && pendingEvaluations.isEmpty()); + } + + private synchronized void runOnePass() throws InterruptedException { + runPassStage(pendingEvaluations, "EVALUATION"); + runPassStage(pendingChanges, "CHANGE"); + } + + private synchronized void runPassStage( + Collection tasks, + String stageName + ) throws InterruptedException { + if (!toDispose.isEmpty()) + throw new IllegalStateException("toDispose is not empty: " + toDispose); + + Collection toDispose = this.toDispose; + + startPassStage(tasks, toDispose, stageName); + sync(); + dispose(toDispose); + } + + private void dispose(Collection toDispose) { + toDispose.forEach(TickerTask::dispose); + toDispose.clear(); + } + + private synchronized void startPassStage( + Collection tasks, + Collection toDispose, + String stageName + ) { + if (tasks.isEmpty()) { + logger.debug("Skipping stage {}: tasks is empty", stageName); + return; + } + + logger.debug("Starting stage {}", stageName); + + if (!cachedTransferList.isEmpty()) + throw new IllegalStateException("cachedTransferList is not empty: " + cachedTransferList); + + workingTickers.set(0); + + for (Ticker ticker : tickers) { + workingTickers.incrementAndGet(); + + Collection selectedTasks = cachedTransferList; + ticker.requestWork(selectTasks(ticker, tasks, selectedTasks)); + selectedTasks.clear(); + } + + toDispose.addAll(tasks); + tasks.clear(); + + logger.debug("Stage started"); + } + + private Collection selectTasks( + Ticker ticker, + Collection tasks, + Collection output + ) { + // TODO implement properly + + for (TickerTask task : tasks) { + // Assign to one ticker randomly + if (task.hashCode() % tickers.size() == ticker.getId()) { + output.add(task); + } + } + + return output; + } + + private synchronized void sync() throws InterruptedException { + logger.debug("Starting sync wait"); + while (workingTickers.get() > 0) { + this.wait(); + logger.debug("Sync notification received"); + } + logger.debug("Sync achieved"); + } + + /* + * Interface for Tickers + */ + + synchronized void reportWorkComplete() { + int stillWorking = workingTickers.decrementAndGet(); + if (stillWorking < 0) + throw new IllegalStateException("stillWorking = " + stillWorking); + + if (stillWorking != 0) { + logger.debug("stillWorking = {}, not notifying sync", stillWorking); + return; + } + + logger.debug("All tickers reported completion, notifying sync"); + + this.notifyAll(); + } + + void crash(Throwable t, String thread) { + if (t instanceof ConcurrentModificationException) { + logger.debug("javahorse kill urself"); + } + + CrashReports.report( + t, + "Something has gone horribly wrong in server ticker code " + + "(thread %s) and it is (probably) not related to mods or devils.", + thread + ); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/ticking/TickerTask.java b/src/main/java/ru/windcorp/progressia/server/world/ticking/TickerTask.java new file mode 100644 index 0000000..afbecc5 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/ticking/TickerTask.java @@ -0,0 +1,49 @@ +package ru.windcorp.progressia.server.world.ticking; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.Coordinates; +import ru.windcorp.progressia.server.Server; + +/** + * A task that can be executed by a Ticker. + * This is a superinterface for {@link Change} and {@link Evaluation} and is not meant to be extended further. + * This interface is used to determine the Ticker that is suitable for the execution of this task. + * @author javapony + */ +public abstract class TickerTask { + + /** + * Returns {@code false} iff this task is thread-safe and may be executed by + * any Ticker. If and only if a task returns {@code true} in this method + * is its {@link #getRelevantChunk(Vec3i)} method invoked. + * @implNote Default implementation returns {@code true}, making this task thread-sensitive + * @return {@code true} iff this task must be run in a Ticker implied by {@link #getRelevantChunk(Vec3i)} + */ + public boolean isThreadSensitive() { + return true; + } + + /** + * Sets {@code output} to be equal to the {@linkplain Coordinates#chunk coordinates of chunk} + * of the chunk that must be owned by the Ticker will execute this task. This method + * is not invoked iff {@link #isThreadSensitive()} returned {@code false}. + * @param output a {@link Vec3i} to set to the requested value + */ + public abstract void getRelevantChunk(Vec3i output); + + /** + * Invoked when this task has completed and will no longer be used. + * This method is guaranteed to be invoked in the main server thread. + * @implNote Default implementation does nothing + */ + public void dispose() { + // Do nothing + } + + /** + * Executes this task. This method is provided for the convenience of Tickers. + * @param server the server to run on + */ + abstract void run(Server server); + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/tile/EdgeTileLogic.java b/src/main/java/ru/windcorp/progressia/server/world/tile/EdgeTileLogic.java new file mode 100644 index 0000000..84311ec --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/tile/EdgeTileLogic.java @@ -0,0 +1,42 @@ +package ru.windcorp.progressia.server.world.tile; + +import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.block.BlockTickContext; + +public class EdgeTileLogic extends TileLogic implements UpdateableTile { + + public EdgeTileLogic(String id) { + super(id); + } + + @Override + public void update(TileTickContext context) { + if (!canOccupyFace(context)) { + context.removeThisTile(); + } + } + + @Override + public boolean canOccupyFace(TileTickContext context) { + boolean canOccupy = false; + + BlockTickContext currentTickContext = context.grabCurrentBlockContext(); + canOccupy ^= canOccupyFace(context, context.getCurrentFace(), currentTickContext); + context.release(currentTickContext); + + BlockTickContext counterTickContext = context.grabCounterBlockContext(); + canOccupy ^= canOccupyFace(context, context.getCounterFace(), counterTickContext); + context.release(counterTickContext); + + return canOccupy; + } + + public boolean canOccupyFace(TileTickContext ownContext, BlockFace blockFace, BlockTickContext blockContext) { + BlockLogic block = blockContext.getBlock(); + if (block == null) return false; + + return block.isSolid(blockContext, ownContext.getCurrentFace()); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/world/tile/ForwardingTileTickContext.java b/src/main/java/ru/windcorp/progressia/server/world/tile/ForwardingTileTickContext.java index 0f2c29e..0f385db 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tile/ForwardingTileTickContext.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tile/ForwardingTileTickContext.java @@ -1,17 +1,9 @@ package ru.windcorp.progressia.server.world.tile; -import java.util.List; - import glm.vec._3.i.Vec3i; -import ru.windcorp.progressia.common.world.ChunkData; -import ru.windcorp.progressia.common.world.WorldData; -import ru.windcorp.progressia.common.world.block.BlockData; import ru.windcorp.progressia.common.world.block.BlockFace; -import ru.windcorp.progressia.common.world.tile.TileData; import ru.windcorp.progressia.server.Server; -import ru.windcorp.progressia.server.world.ChunkLogic; import ru.windcorp.progressia.server.world.WorldLogic; -import ru.windcorp.progressia.server.world.block.BlockLogic; public class ForwardingTileTickContext implements TileTickContext { @@ -29,16 +21,6 @@ public class ForwardingTileTickContext implements TileTickContext { this.parent = parent; } - @Override - public ChunkLogic getChunk() { - return parent.getChunk(); - } - - @Override - public ChunkData getChunkData() { - return parent.getChunkData(); - } - @Override public double getTickLength() { return parent.getTickLength(); @@ -55,78 +37,23 @@ public class ForwardingTileTickContext implements TileTickContext { } @Override - public WorldData getWorldData() { - return parent.getWorldData(); + public Vec3i getCurrentBlockInWorld() { + return parent.getCurrentBlockInWorld(); + } + + @Override + public Vec3i getCounterBlockInWorld() { + return parent.getCounterBlockInWorld(); } @Override - public void requestBlockTick(Vec3i blockInWorld) { - parent.requestBlockTick(blockInWorld); + public BlockFace getCurrentFace() { + return parent.getCurrentFace(); } @Override - public void requestTileTick(Vec3i blockInWorld, BlockFace face, int layer) { - parent.requestTileTick(blockInWorld, face, layer); - } - - @Override - public Vec3i getCoords() { - return parent.getCoords(); - } - - @Override - public Vec3i getChunkCoords() { - return parent.getChunkCoords(); - } - - @Override - public BlockFace getFace() { - return parent.getFace(); - } - - @Override - public int getLayer() { - return parent.getLayer(); - } - - @Override - public TileLogic getTile() { - return parent.getTile(); - } - - @Override - public TileData getTileData() { - return parent.getTileData(); - } - - @Override - public List getTiles() { - return parent.getTiles(); - } - - @Override - public List getTilesOrNull() { - return parent.getTilesOrNull(); - } - - @Override - public List getTileDataList() { - return parent.getTileDataList(); - } - - @Override - public List getTileDataListOrNull() { - return parent.getTileDataListOrNull(); - } - - @Override - public BlockLogic getBlock() { - return parent.getBlock(); - } - - @Override - public BlockData getBlockData() { - return parent.getBlockData(); + public int getCurrentLayer() { + return parent.getCurrentLayer(); } } diff --git a/src/main/java/ru/windcorp/progressia/server/world/tile/TickableTile.java b/src/main/java/ru/windcorp/progressia/server/world/tile/TickableTile.java index d078ba0..2389523 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tile/TickableTile.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tile/TickableTile.java @@ -1,10 +1,8 @@ package ru.windcorp.progressia.server.world.tile; -import ru.windcorp.progressia.server.world.Changer; - public interface TickableTile { - void tick(TileTickContext context, Changer changer); + void tick(TileTickContext context); default boolean doesTickRegularly(TileTickContext context) { return false; diff --git a/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogic.java b/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogic.java index c44ec7e..86e62d3 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tile/TileLogic.java @@ -10,7 +10,7 @@ public class TileLogic extends Namespaced { } public boolean canOccupyFace(TileTickContext context) { - return canOccupyFace(context.getFace()); + return canOccupyFace(context.getCurrentFace()); } public boolean canOccupyFace(BlockFace face) { diff --git a/src/main/java/ru/windcorp/progressia/server/world/tile/TileTickContext.java b/src/main/java/ru/windcorp/progressia/server/world/tile/TileTickContext.java index 0b80b5f..30e8e60 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tile/TileTickContext.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tile/TileTickContext.java @@ -3,69 +3,158 @@ package ru.windcorp.progressia.server.world.tile; import java.util.List; import glm.vec._3.i.Vec3i; +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.BlockData; import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.common.world.tile.TileData; -import ru.windcorp.progressia.server.world.ChunkTickContext; +import ru.windcorp.progressia.server.world.ChunkLogic; +import ru.windcorp.progressia.server.world.TickAndUpdateUtil; +import ru.windcorp.progressia.server.world.TickContext; import ru.windcorp.progressia.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.block.BlockTickContext; -public interface TileTickContext extends ChunkTickContext { +public interface TileTickContext extends TickContext { + + /* + * Specifications + */ /** * Returns the current world coordinates. * @return the world coordinates of the tile being ticked */ - Vec3i getCoords(); + Vec3i getCurrentBlockInWorld(); /** - * Returns the current chunk coordinates. - * @return the chunk coordinates of the tile being ticked + * Returns the counter world coordinates. + * @return the world coordinates of the tile being ticked */ - Vec3i getChunkCoords(); + Vec3i getCounterBlockInWorld(); /** - * Returns the current block face. This face is always - * {@linkplain BlockFace#isPrimary() primary}. + * Returns the current block face. * @return the block face that the tile being ticked occupies */ - BlockFace getFace(); + BlockFace getCurrentFace(); /** * Returns the current layer. * @return the layer that the tile being ticked occupies in the tile stack */ - int getLayer(); + int getCurrentLayer(); + + default BlockFace getCounterFace() { + return getCurrentFace().getCounter(); + } + + /* + * Tile-related + */ default TileLogic getTile() { - return getTiles().get(getLayer()); + return getTiles().get(getCurrentLayer()); } default TileData getTileData() { - return getTileDataList().get(getLayer()); + return getTileDataList().get(getCurrentLayer()); } default List getTiles() { - return getChunk().getTiles(getChunkCoords(), getFace()); + Vec3i blockInChunk = Vectors.grab3i(); + Coordinates.convertInWorldToInChunk(getCurrentBlockInWorld(), blockInChunk); + List result = getCurrentChunk().getTiles(blockInChunk, getCurrentFace()); + Vectors.release(blockInChunk); + return result; } default List getTilesOrNull() { - return getChunk().getTilesOrNull(getChunkCoords(), getFace()); + Vec3i blockInChunk = Vectors.grab3i(); + Coordinates.convertInWorldToInChunk(getCurrentBlockInWorld(), blockInChunk); + List result = getCurrentChunk().getTilesOrNull(blockInChunk, getCurrentFace()); + Vectors.release(blockInChunk); + return result; } default List getTileDataList() { - return getChunkData().getTiles(getChunkCoords(), getFace()); + Vec3i blockInChunk = Vectors.grab3i(); + Coordinates.convertInWorldToInChunk(getCurrentBlockInWorld(), blockInChunk); + List result = getCurrentChunkData().getTiles(blockInChunk, getCurrentFace()); + Vectors.release(blockInChunk); + return result; } default List getTileDataListOrNull() { - return getChunkData().getTilesOrNull(getChunkCoords(), getFace()); + Vec3i blockInChunk = Vectors.grab3i(); + Coordinates.convertInWorldToInChunk(getCurrentBlockInWorld(), blockInChunk); + List result = getCurrentChunkData().getTilesOrNull(blockInChunk, getCurrentFace()); + Vectors.release(blockInChunk); + return result; } - default BlockLogic getBlock() { - return getChunk().getBlock(getChunkCoords()); + /* + * Current block/chunk + */ + + default ChunkLogic getCurrentChunk() { + return getWorld().getChunkByBlock(getCurrentBlockInWorld()); } - default BlockData getBlockData() { - return getChunkData().getBlock(getChunkCoords()); + default ChunkData getCurrentChunkData() { + return getWorldData().getChunkByBlock(getCurrentBlockInWorld()); + } + + default BlockLogic getCurrentBlock() { + return getWorld().getBlock(getCurrentBlockInWorld()); + } + + default BlockData getCurrentBlockData() { + return getWorldData().getBlock(getCurrentBlockInWorld()); + } + + default BlockTickContext grabCurrentBlockContext() { + return TickAndUpdateUtil.grabBlockTickContext(getServer(), getCurrentBlockInWorld()); + } + + /* + * Counter block/chunk + */ + + default ChunkLogic getCounterChunk() { + return getWorld().getChunkByBlock(getCounterBlockInWorld()); + } + + default ChunkData getCounterChunkData() { + return getWorldData().getChunkByBlock(getCounterBlockInWorld()); + } + + default BlockLogic getCounterBlock() { + return getWorld().getBlock(getCounterBlockInWorld()); + } + + default BlockData getCounterBlockData() { + return getWorldData().getBlock(getCounterBlockInWorld()); + } + + default BlockTickContext grabCounterBlockContext() { + return TickAndUpdateUtil.grabBlockTickContext(getServer(), getCounterBlockInWorld()); + } + + /* + * Convenience methods - changes + */ + + default void removeThisTile() { + getAccessor().removeTile(getCurrentBlockInWorld(), getCurrentFace(), getTileData()); + } + + /* + * Misc + */ + + default void release(BlockTickContext context) { + TickAndUpdateUtil.releaseTickContext(context); } } diff --git a/src/main/java/ru/windcorp/progressia/server/world/tile/UpdatableTile.java b/src/main/java/ru/windcorp/progressia/server/world/tile/UpdatableTile.java deleted file mode 100644 index 00613f0..0000000 --- a/src/main/java/ru/windcorp/progressia/server/world/tile/UpdatableTile.java +++ /dev/null @@ -1,9 +0,0 @@ -package ru.windcorp.progressia.server.world.tile; - -import ru.windcorp.progressia.server.world.Changer; - -public interface UpdatableTile { - - void update(TileTickContext context, Changer changer); - -} diff --git a/src/main/java/ru/windcorp/progressia/server/world/tile/UpdateableTile.java b/src/main/java/ru/windcorp/progressia/server/world/tile/UpdateableTile.java new file mode 100644 index 0000000..e96d3cd --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/tile/UpdateableTile.java @@ -0,0 +1,7 @@ +package ru.windcorp.progressia.server.world.tile; + +public interface UpdateableTile { + + void update(TileTickContext context); + +} diff --git a/src/main/java/ru/windcorp/progressia/test/TestBlockLogicAir.java b/src/main/java/ru/windcorp/progressia/test/TestBlockLogicAir.java new file mode 100644 index 0000000..6ab642e --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/TestBlockLogicAir.java @@ -0,0 +1,22 @@ +package ru.windcorp.progressia.test; + +import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.server.world.block.BlockLogic; + +public class TestBlockLogicAir extends BlockLogic { + + public TestBlockLogicAir(String id) { + super(id); + } + + @Override + public boolean isSolid(BlockFace face) { + return false; + } + + @Override + public boolean isTransparent() { + return true; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/TestContent.java b/src/main/java/ru/windcorp/progressia/test/TestContent.java index 6100cbd..ca13b4e 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestContent.java +++ b/src/main/java/ru/windcorp/progressia/test/TestContent.java @@ -54,7 +54,7 @@ public class TestContent { } }); register(new BlockRenderNone("Test:Air")); - register(new BlockLogic("Test:Air")); + register(new TestBlockLogicAir("Test:Air")); register(new BlockData("Test:Dirt")); register(new BlockRenderOpaqueCube("Test:Dirt", getBlockTexture("dirt"))); @@ -76,19 +76,19 @@ public class TestContent { private static void registerTiles() { register(new TileData("Test:Grass")); register(new TileRenderGrass("Test:Grass", getTileTexture("grass_top"), getTileTexture("grass_side"))); - register(new TileLogic("Test:Grass")); + register(new TestTileLogicGrass("Test:Grass")); register(new TileData("Test:Stones")); register(new TileRenderSimple("Test:Stones", getTileTexture("stones"))); - register(new TileLogic("Test:Stones")); + register(new EdgeTileLogic("Test:Stones")); register(new TileData("Test:YellowFlowers")); register(new TileRenderSimple("Test:YellowFlowers", getTileTexture("yellow_flowers"))); - register(new TileLogic("Test:YellowFlowers")); + register(new EdgeTileLogic("Test:YellowFlowers")); register(new TileData("Test:Sand")); register(new TileRenderSimple("Test:Sand", getTileTexture("sand"))); - register(new TileLogic("Test:Sand")); + register(new EdgeTileLogic("Test:Sand")); } private static void registerEntities() { @@ -125,7 +125,7 @@ public class TestContent { block = BlockDataRegistry.getInstance().get("Test:Stone"); } - server.getAdHocChanger().setBlock(z000, block); + server.getWorldAccessor().setBlock(z000, block); })); data.register("Test:BreakBlock", ControlBreakBlockData::new); @@ -222,7 +222,7 @@ public class TestContent { private static void onBlockBreakReceived(Server server, PacketControl packet, ru.windcorp.progressia.server.comms.Client client) { Vec3i blockInWorld = ((ControlBreakBlockData) packet.getControl()).getBlockInWorld(); - server.getAdHocChanger().setBlock(blockInWorld, BlockDataRegistry.getInstance().get("Test:Air")); + server.getWorldAccessor().setBlock(blockInWorld, BlockDataRegistry.getInstance().get("Test:Air")); } private static void onBlockPlaceTrigger(ControlData control) { @@ -238,7 +238,7 @@ public class TestContent { private static void onBlockPlaceReceived(Server server, PacketControl packet, ru.windcorp.progressia.server.comms.Client client) { Vec3i blockInWorld = ((ControlPlaceBlockData) packet.getControl()).getBlockInWorld(); if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null) return; - server.getAdHocChanger().setBlock(blockInWorld, BlockDataRegistry.getInstance().get("Test:Stone")); + server.getWorldAccessor().setBlock(blockInWorld, BlockDataRegistry.getInstance().get("Test:Stone")); } } diff --git a/src/main/java/ru/windcorp/progressia/test/TestEntityLogicStatie.java b/src/main/java/ru/windcorp/progressia/test/TestEntityLogicStatie.java index 9918f07..8577503 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestEntityLogicStatie.java +++ b/src/main/java/ru/windcorp/progressia/test/TestEntityLogicStatie.java @@ -1,7 +1,6 @@ package ru.windcorp.progressia.test; import ru.windcorp.progressia.common.world.entity.EntityData; -import ru.windcorp.progressia.server.world.Changer; import ru.windcorp.progressia.server.world.TickContext; import ru.windcorp.progressia.server.world.entity.EntityLogic; @@ -12,13 +11,13 @@ public class TestEntityLogicStatie extends EntityLogic { } @Override - public void tick(EntityData entity, TickContext context, Changer changer) { - super.tick(entity, context, changer); + public void tick(EntityData entity, TickContext context) { + super.tick(entity, context); TestEntityDataStatie statie = (TestEntityDataStatie) entity; int size = (int) (18 + 6 * Math.sin(entity.getAge())); - changer.changeEntity(statie, e -> e.setSizeNow(size)); + context.getServer().getWorldAccessor().changeEntity(statie, e -> e.setSizeNow(size)); } } diff --git a/src/main/java/ru/windcorp/progressia/test/TestTileLogicGrass.java b/src/main/java/ru/windcorp/progressia/test/TestTileLogicGrass.java new file mode 100644 index 0000000..1f030e2 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/TestTileLogicGrass.java @@ -0,0 +1,58 @@ +package ru.windcorp.progressia.test; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.server.Server; +import ru.windcorp.progressia.server.world.TickAndUpdateUtil; +import ru.windcorp.progressia.server.world.block.BlockLogic; +import ru.windcorp.progressia.server.world.block.BlockTickContext; +import ru.windcorp.progressia.server.world.tile.EdgeTileLogic; +import ru.windcorp.progressia.server.world.tile.TileTickContext; + +public class TestTileLogicGrass extends EdgeTileLogic { + + public TestTileLogicGrass(String id) { + super(id); + } + + private boolean isBlockAboveTransparent(Server server, Vec3i blockInWorld) { + Vec3i blockAboveCoords = Vectors.grab3i(); + blockAboveCoords.set(blockInWorld.x, blockInWorld.y, blockInWorld.z); + blockAboveCoords.add(BlockFace.TOP.getVector()); + + BlockTickContext blockAboveContext = TickAndUpdateUtil.grabBlockTickContext(server, blockAboveCoords); + + try { + BlockLogic blockAbove = blockAboveContext.getBlock(); + if (blockAbove == null) return true; + + return blockAbove.isTransparent(blockAboveContext); + } finally { + TickAndUpdateUtil.releaseTickContext(blockAboveContext); + Vectors.release(blockAboveCoords); + } + } + + @Override + public void update(TileTickContext context) { + super.update(context); + + if ( + !( + context.getCurrentFace() == BlockFace.BOTTOM + || + isBlockAboveTransparent(context.getServer(), context.getCurrentBlockInWorld()) + ) + || + !( + context.getCounterFace() == BlockFace.BOTTOM + || + isBlockAboveTransparent(context.getServer(), context.getCounterBlockInWorld()) + ) + ) { + context.removeThisTile(); + } + } + +} diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index c40552b..ce70734 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -8,12 +8,12 @@ - + - + @@ -21,6 +21,12 @@ + + +