Dumped server ticking code into the Mariana Trench and rewritten it

No regrets.

- Pretty much everything related to ticking changed
- Ticking is now capable of running in parallel (other code - not so
much)
- Implemented world updates
- Broke some stuff
This commit is contained in:
OLEGSHA 2020-11-27 00:56:56 +03:00
parent 6d6e0f6ca4
commit 44de6a86f7
53 changed files with 1786 additions and 710 deletions

View File

@ -1,7 +1,6 @@
package ru.windcorp.progressia.client.audio.backend; package ru.windcorp.progressia.client.audio.backend;
import org.lwjgl.BufferUtils; import org.lwjgl.BufferUtils;
import ru.windcorp.progressia.client.audio.backend.SoundType;
import ru.windcorp.progressia.common.resource.*; import ru.windcorp.progressia.common.resource.*;
import java.nio.IntBuffer; import java.nio.IntBuffer;

View File

@ -26,15 +26,6 @@ public class SoundType extends Namespaced {
alBufferData(audioBuffer, format, rawAudio, sampleRate); 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) { public void initSpeaker(Speaker speaker) {
speaker.setAudioData(audioBuffer); speaker.setAudioData(audioBuffer);
} }

View File

@ -1,6 +1,5 @@
package ru.windcorp.progressia.common.util.crash.providers; package ru.windcorp.progressia.common.util.crash.providers;
import ru.windcorp.progressia.Progressia;
import ru.windcorp.progressia.ProgressiaLauncher; import ru.windcorp.progressia.ProgressiaLauncher;
import ru.windcorp.progressia.common.util.crash.ContextProvider; import ru.windcorp.progressia.common.util.crash.ContextProvider;

View File

@ -210,7 +210,11 @@ public class ChunkData {
private void createNormalTileContainer(Vec3i blockInChunk, BlockFace face) { private void createNormalTileContainer(Vec3i blockInChunk, BlockFace face) {
List<TileData> primaryList = List<TileData> primaryList =
SizeLimitedList.wrap( SizeLimitedList.wrap(
new ArrayList<>(TILES_PER_FACE), TILES_PER_FACE new ChunkDataReportingList(
new ArrayList<>(TILES_PER_FACE),
this, blockInChunk, face
),
TILES_PER_FACE
); );
List<TileData> secondaryList = Lists.reverse(primaryList); List<TileData> secondaryList = Lists.reverse(primaryList);

View File

@ -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<TileData> {
private final List<TileData> parent;
private final ChunkData reportTo;
private final Vec3i blockInChunk;
private final BlockFace face;
public ChunkDataReportingList(List<TileData> 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);
});
}
}

View File

@ -120,6 +120,15 @@ public final class BlockFace extends BlockRelation {
return counterFace; return counterFace;
} }
public BlockFace getCounter() {
return counterFace;
}
public BlockFace getCounterAndMoveCursor(Vec3i cursor) {
cursor.add(getVector());
return counterFace;
}
public int getId() { public int getId() {
return id; return id;
} }

View File

@ -1,12 +1,18 @@
package ru.windcorp.progressia.server; 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.jputil.functions.ThrowingRunnable;
import ru.windcorp.progressia.common.util.TaskQueue; import ru.windcorp.progressia.common.util.TaskQueue;
import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.server.comms.ClientManager; 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.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 { public class Server {
@ -15,18 +21,20 @@ public class Server {
} }
private final WorldLogic world; private final WorldLogic world;
private final ImplementedChangeTracker adHocChanger = private final WorldAccessor worldAccessor = new WorldAccessor(this);
new ImplementedChangeTracker();
private final ServerThread serverThread; private final ServerThread serverThread;
private final ClientManager clientManager = new ClientManager(this); private final ClientManager clientManager = new ClientManager(this);
private final TaskQueue taskQueue = new TaskQueue(this::isServerThread); private final TaskQueue taskQueue = new TaskQueue(this::isServerThread);
private final Collection<Consumer<Server>> repeatingTasks = Collections.synchronizedCollection(new ArrayList<>());
public Server(WorldData world) { public Server(WorldData world) {
this.world = new WorldLogic(world); this.world = new WorldLogic(world, this);
this.serverThread = new ServerThread(this); this.serverThread = new ServerThread(this);
invokeEveryTick(this::scheduleChunkTicks);
} }
public WorldLogic getWorld() { 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() { public ClientManager getClientManager() {
return clientManager; 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() { public boolean isServerThread() {
return getCurrentServer() == this; 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.
*
* <p>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) { public void invokeLater(Runnable task) {
taskQueue.invokeLater(task); taskQueue.invokeLater(task);
} }
/**
* Executes the tasks in the server main thread as soon as possible.
*
* <p>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)}.
*
* <p>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) { public void invokeNow(Runnable task) {
taskQueue.invokeNow(task); taskQueue.invokeNow(task);
} }
@ -62,17 +98,41 @@ public class Server {
taskQueue.waitAndInvoke(task); taskQueue.waitAndInvoke(task);
} }
public void invokeEveryTick(Consumer<Server> 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() { public void start() {
this.serverThread.start(); this.serverThread.start();
} }
public void tick() { public void tick() {
taskQueue.runTasks(); taskQueue.runTasks();
adHocChanger.applyChanges(this); repeatingTasks.forEach(t -> t.accept(this));
} }
public void shutdown(String message) { public void shutdown(String message) {
// Do nothing // Do nothing
} }
private void scheduleChunkTicks(Server server) {
}
} }

View File

@ -3,10 +3,9 @@ package ru.windcorp.progressia.server;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.LogManager; 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 { public class ServerThread implements Runnable {
@ -39,14 +38,15 @@ public class ServerThread implements Runnable {
r -> new Thread(new ServerThreadTracker(r), "Server thread") r -> new Thread(new ServerThreadTracker(r), "Server thread")
); );
private final Ticker ticker; private final TickerCoordinator ticker;
public ServerThread(Server server) { public ServerThread(Server server) {
this.server = server; this.server = server;
this.ticker = new Ticker(server); this.ticker = new TickerCoordinator(server, 1);
} }
public void start() { public void start() {
ticker.start();
executor.scheduleAtFixedRate(this, 0, 1000 / 20, TimeUnit.MILLISECONDS); executor.scheduleAtFixedRate(this, 0, 1000 / 20, TimeUnit.MILLISECONDS);
} }
@ -54,7 +54,7 @@ public class ServerThread implements Runnable {
public void run() { public void run() {
try { try {
server.tick(); server.tick();
ticker.run(); ticker.runOneTick();
} catch (Exception e) { } catch (Exception e) {
LogManager.getLogger(getClass()).error("Got an exception in server thread", e); LogManager.getLogger(getClass()).error("Got an exception in server thread", e);
} }
@ -64,4 +64,8 @@ public class ServerThread implements Runnable {
return server; return server;
} }
public TickerCoordinator getTicker() {
return ticker;
}
} }

View File

@ -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<T> {
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);
<T extends EntityData> void changeEntity(T entity, Change<T> change);
}

View File

@ -12,7 +12,9 @@ import com.google.common.collect.Lists;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.world.tile.TileLocation; 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.ChunkData;
import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.tile.TileData; import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.server.world.block.BlockLogic; import ru.windcorp.progressia.server.world.block.BlockLogic;
@ -47,34 +49,33 @@ public class ChunkLogic {
MutableTileTickContext tileTickContext = MutableTileTickContext tileTickContext =
new MutableTileTickContext(); new MutableTileTickContext();
blockTickContext.setWorld(getWorld());
blockTickContext.setChunk(this);
tileTickContext.setWorld(getWorld());
tileTickContext.setChunk(this);
data.forEachBlock(blockInChunk -> { data.forEachBlock(blockInChunk -> {
BlockLogic block = getBlock(blockInChunk); BlockLogic block = getBlock(blockInChunk);
if (block instanceof TickableBlock) { if (block instanceof TickableBlock) {
blockTickContext.setCoordsInChunk(blockInChunk); Vec3i blockInWorld = Vectors.grab3i();
Coordinates.getInWorld(getData().getPosition(), blockInChunk, blockInWorld);
if (((TickableBlock) block) blockTickContext.init(getWorld().getServer(), blockInWorld);
.doesTickRegularly(blockTickContext)
) { Vectors.release(blockInWorld);
if (((TickableBlock) block).doesTickRegularly(blockTickContext)) {
tickingBlocks.add(new Vec3i(blockInChunk)); tickingBlocks.add(new Vec3i(blockInChunk));
} }
} }
}); });
data.forEachTile((loc, tileData) -> { data.forEachTile((loc, tileData) -> {
TileLogic tile = TileLogic tile = TileLogicRegistry.getInstance().get(tileData.getId());
TileLogicRegistry.getInstance().get(tileData.getId());
if (tile instanceof TickableTile) { if (tile instanceof TickableTile) {
tileTickContext.setCoordsInChunk(loc.pos); Vec3i blockInWorld = Vectors.grab3i();
tileTickContext.setFace(loc.face); Coordinates.getInWorld(getData().getPosition(), loc.pos, blockInWorld);
tileTickContext.setLayer(loc.layer);
tileTickContext.init(getWorld().getServer(), blockInWorld, loc.face, loc.layer);
Vectors.release(blockInWorld);
if (((TickableTile) tile).doesTickRegularly(tileTickContext)) { if (((TickableTile) tile).doesTickRegularly(tileTickContext)) {
tickingTiles.add(new TileLocation(loc)); tickingTiles.add(new TileLocation(loc));
@ -109,8 +110,7 @@ public class ChunkLogic {
tickingTiles.forEach(location -> { tickingTiles.forEach(location -> {
action.accept( action.accept(
location, location,
getTilesOrNull(location.pos, location.face) getTilesOrNull(location.pos, location.face).get(location.layer)
.get(location.layer)
); );
}); });
} }

View File

@ -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<TileData> 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 <T extends EntityData> void set(T entity, Change<T> 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<EntityData>) 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<ChangeImplementation> changes = new ArrayList<>(1024);
private final LowOverheadCache<SetBlock> setBlockCache =
new LowOverheadCache<>(SetBlock::new);
private final LowOverheadCache<AddOrRemoveTile> addOrRemoveTileCache =
new LowOverheadCache<>(AddOrRemoveTile::new);
private final LowOverheadCache<ChangeEntity> 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 <T extends EntityData> void changeEntity(
T entity, Change<T> 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);
}
}
}

View File

@ -3,41 +3,42 @@ package ru.windcorp.progressia.server.world;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.Coordinates; import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.world.block.BlockTickContext; import ru.windcorp.progressia.server.world.block.BlockTickContext;
public class MutableBlockTickContext public class MutableBlockTickContext
extends MutableChunkTickContext extends MutableTickContext
implements BlockTickContext { implements BlockTickContext {
private final Vec3i blockInWorld = new Vec3i(); private final Vec3i blockInWorld = new Vec3i();
private final Vec3i blockInChunk = new Vec3i(); private ChunkLogic chunk;
@Override @Override
public Vec3i getCoords() { public Vec3i getBlockInWorld() {
return this.blockInWorld; return this.blockInWorld;
} }
@Override public void setCoordsInWorld(Vec3i blockInWorld) {
public Vec3i getChunkCoords() { getBlockInWorld().set(blockInWorld.x, blockInWorld.y, blockInWorld.z);
return this.blockInChunk;
}
public void setCoordsInWorld(Vec3i coords) {
getCoords().set(coords.x, coords.y, coords.z);
Coordinates.convertInWorldToInChunk(getCoords(), getChunkCoords());
Vec3i chunk = Vectors.grab3i(); Vec3i chunk = Vectors.grab3i();
Coordinates.convertInWorldToChunk(coords, chunk); Coordinates.convertInWorldToChunk(blockInWorld, chunk);
setChunk(getWorld().getChunk(chunk)); setChunk(getWorld().getChunk(chunk));
Vectors.release(chunk); Vectors.release(chunk);
} }
public void setCoordsInChunk(Vec3i coords) { @Override
getChunkCoords().set(coords.x, coords.y, coords.z); public ChunkLogic getChunk() {
Coordinates.getInWorld( return chunk;
getChunkData().getPosition(), getChunkCoords(), }
getCoords()
); public void setChunk(ChunkLogic chunk) {
this.chunk = chunk;
}
public void init(Server server, Vec3i blockInWorld) {
setServer(server);
setCoordsInWorld(blockInWorld);
} }
} }

View File

@ -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");
}
}

View File

@ -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;
}
}

View File

@ -1,60 +1,47 @@
package ru.windcorp.progressia.server.world; package ru.windcorp.progressia.server.world;
import glm.vec._3.i.Vec3i; 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.common.world.block.BlockFace;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.world.tile.TileTickContext; import ru.windcorp.progressia.server.world.tile.TileTickContext;
public class MutableTileTickContext public class MutableTileTickContext
extends MutableChunkTickContext extends MutableTickContext
implements TileTickContext { implements TileTickContext {
private final Vec3i blockInWorld = new Vec3i(); private final Vec3i currentBlockInWorld = new Vec3i();
private final Vec3i blockInChunk = new Vec3i(); private final Vec3i counterBlockInWorld = new Vec3i();
private BlockFace face; private BlockFace face;
private int layer; private int layer;
@Override @Override
public Vec3i getCoords() { public Vec3i getCurrentBlockInWorld() {
return this.blockInWorld; return this.currentBlockInWorld;
} }
@Override @Override
public Vec3i getChunkCoords() { public Vec3i getCounterBlockInWorld() {
return this.blockInChunk; return this.counterBlockInWorld;
} }
public void setCoordsInWorld(Vec3i coords) { public void setCoordsInWorld(Vec3i currentBlockInWorld) {
getCoords().set(coords.x, coords.y, coords.z); getCurrentBlockInWorld().set(currentBlockInWorld.x, currentBlockInWorld.y, currentBlockInWorld.z);
Coordinates.convertInWorldToInChunk(getCoords(), getChunkCoords()); getCounterBlockInWorld().set(currentBlockInWorld.x, currentBlockInWorld.y, currentBlockInWorld.z).add(getCurrentFace().getVector());
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()
);
} }
@Override @Override
public BlockFace getFace() { public BlockFace getCurrentFace() {
return face; return face;
} }
public void setFace(BlockFace face) { public void setFace(BlockFace face) {
this.face = face; this.face = face;
setCoordsInWorld(getCurrentBlockInWorld());
} }
@Override @Override
public int getLayer() { public int getCurrentLayer() {
return layer; return layer;
} }
@ -62,4 +49,11 @@ implements TileTickContext {
this.layer = layer; this.layer = layer;
} }
public void init(Server server, Vec3i blockInWorld, BlockFace face, int layer) {
setServer(server);
setFace(face);
setCoordsInWorld(blockInWorld);
setLayer(layer);
}
} }

View File

@ -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<MutableBlockTickContext> JAVAPONY_S_ULTIMATE_BLOCK_TICK_CONTEXT_SUPPLY =
new LowOverheadCache<>(MutableBlockTickContext::new);
private static final LowOverheadCache<MutableTileTickContext> 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<TileLogic> 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<TileLogic> 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() {}
}

View File

@ -1,9 +1,8 @@
package ru.windcorp.progressia.server.world; 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.WorldData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.server.Server; import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.world.tasks.WorldAccessor;
public interface TickContext { public interface TickContext {
@ -15,12 +14,12 @@ public interface TickContext {
return getServer().getWorld(); return getServer().getWorld();
} }
default WorldAccessor getAccessor() {
return getServer().getWorldAccessor();
}
default WorldData getWorldData() { default WorldData getWorldData() {
return getWorld().getData(); return getWorld().getData();
} }
void requestBlockTick(Vec3i blockInWorld);
void requestTileTick(Vec3i blockInWorld, BlockFace face, int layer);
} }

View File

@ -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);
}
}

View File

@ -2,24 +2,34 @@ package ru.windcorp.progressia.server.world;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.ChunkData; 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.Coordinates;
import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.WorldDataListener; 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.block.BlockLogic;
import ru.windcorp.progressia.server.world.tile.TileLogic;
public class WorldLogic { public class WorldLogic {
private final WorldData data; private final WorldData data;
private final Server server;
private final Map<ChunkData, ChunkLogic> chunks = new HashMap<>(); private final Map<ChunkData, ChunkLogic> chunks = new HashMap<>();
public WorldLogic(WorldData data) { public WorldLogic(WorldData data, Server server) {
this.data = data; this.data = data;
this.server = server;
data.addListener(new WorldDataListener() { data.addListener(new WorldDataListener() {
@Override @Override
@ -32,6 +42,33 @@ public class WorldLogic {
chunks.remove(chunk); 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() { public WorldData getData() {
@ -61,10 +98,42 @@ public class WorldLogic {
Vec3i blockInChunk = Vectors.grab3i(); Vec3i blockInChunk = Vectors.grab3i();
Coordinates.convertInWorldToInChunk(blockInWorld, blockInChunk); Coordinates.convertInWorldToInChunk(blockInWorld, blockInChunk);
BlockLogic result = chunk.getBlock(blockInChunk); BlockLogic result = chunk.getBlock(blockInChunk);
Vectors.release(blockInChunk);
return result;
}
public List<TileLogic> getTiles(Vec3i blockInWorld, BlockFace face) {
return getTilesImpl(blockInWorld, face, true);
}
public List<TileLogic> getTilesOrNull(Vec3i blockInWorld, BlockFace face) {
return getTilesImpl(blockInWorld, face, false);
}
private List<TileLogic> getTilesImpl(Vec3i blockInWorld, BlockFace face, boolean createIfMissing) {
ChunkLogic chunk = getChunkByBlock(blockInWorld);
if (chunk == null) return null;
Vec3i blockInChunk = Vectors.grab3i();
Coordinates.convertInWorldToInChunk(blockInWorld, blockInChunk);
List<TileLogic> result =
createIfMissing
? chunk.getTiles(blockInChunk, face)
: chunk.getTilesOrNull(blockInChunk, face);
Vectors.release(blockInChunk);
return result; return result;
} }
public TileLogic getTile(Vec3i blockInWorld, BlockFace face, int layer) {
List<TileLogic> tiles = getTilesOrNull(blockInWorld, face);
if (tiles == null || tiles.size() <= layer) return null;
return tiles.get(layer);
}
public Collection<ChunkLogic> getChunks() { public Collection<ChunkLogic> getChunks() {
return chunks.values(); return chunks.values();
} }

View File

@ -1,6 +1,7 @@
package ru.windcorp.progressia.server.world.block; package ru.windcorp.progressia.server.world.block;
import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class BlockLogic extends Namespaced { public class BlockLogic extends Namespaced {
@ -8,4 +9,20 @@ public class BlockLogic extends Namespaced {
super(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;
}
} }

View File

@ -10,20 +10,14 @@ public interface BlockTickContext extends ChunkTickContext {
* Returns the current world coordinates. * Returns the current world coordinates.
* @return the world coordinates of the block being ticked * @return the world coordinates of the block being ticked
*/ */
Vec3i getCoords(); Vec3i getBlockInWorld();
/**
* Returns the current chunk coordinates.
* @return the chunk coordinates of the block being ticked
*/
Vec3i getChunkCoords();
default BlockLogic getBlock() { default BlockLogic getBlock() {
return getChunk().getBlock(getChunkCoords()); return getWorld().getBlock(getBlockInWorld());
} }
default BlockData getBlockData() { default BlockData getBlockData() {
return getChunkData().getBlock(getChunkCoords()); return getWorldData().getBlock(getBlockInWorld());
} }
} }

View File

@ -1,13 +1,8 @@
package ru.windcorp.progressia.server.world.block; package ru.windcorp.progressia.server.world.block;
import glm.vec._3.i.Vec3i; 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.Server;
import ru.windcorp.progressia.server.world.ChunkLogic; import ru.windcorp.progressia.server.world.ChunkLogic;
import ru.windcorp.progressia.server.world.WorldLogic;
public class ForwardingBlockTickContext implements BlockTickContext { public class ForwardingBlockTickContext implements BlockTickContext {
@ -30,11 +25,6 @@ public class ForwardingBlockTickContext implements BlockTickContext {
return parent.getChunk(); return parent.getChunk();
} }
@Override
public ChunkData getChunkData() {
return parent.getChunkData();
}
@Override @Override
public double getTickLength() { public double getTickLength() {
return parent.getTickLength(); return parent.getTickLength();
@ -46,43 +36,8 @@ public class ForwardingBlockTickContext implements BlockTickContext {
} }
@Override @Override
public Vec3i getCoords() { public Vec3i getBlockInWorld() {
return parent.getCoords(); return parent.getBlockInWorld();
}
@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();
} }
} }

View File

@ -1,10 +1,8 @@
package ru.windcorp.progressia.server.world.block; package ru.windcorp.progressia.server.world.block;
import ru.windcorp.progressia.server.world.Changer;
public interface TickableBlock { public interface TickableBlock {
void tick(BlockTickContext context, Changer changer); void tick(BlockTickContext context);
default boolean doesTickRegularly(BlockTickContext context) { default boolean doesTickRegularly(BlockTickContext context) {
return false; return false;

View File

@ -1,9 +1,11 @@
package ru.windcorp.progressia.server.world.block; package ru.windcorp.progressia.server.world.block;
import ru.windcorp.progressia.server.world.Changer; import org.apache.logging.log4j.LogManager;
public interface UpdateableBlock { 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);
}
} }

View File

@ -2,7 +2,6 @@ package ru.windcorp.progressia.server.world.entity;
import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.entity.EntityData; 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.TickContext;
public class EntityLogic extends Namespaced { public class EntityLogic extends Namespaced {
@ -11,7 +10,7 @@ public class EntityLogic extends Namespaced {
super(id); super(id);
} }
public void tick(EntityData entity, TickContext context, Changer changer) { public void tick(EntityData entity, TickContext context) {
entity.incrementAge(context.getTickLength()); entity.incrementAge(context.getTickLength());
} }

View File

@ -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<? super CachedChange> 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<TileData> 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;
}
}

View File

@ -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<? super CachedEvaluation> 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);
}
}

View File

@ -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<? super CachedChange> disposer;
public CachedChange(Consumer<? super CachedChange> disposer) {
this.disposer = disposer;
}
@Override
public void dispose() {
disposer.accept(this);
}
}

View File

@ -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<? super CachedEvaluation> disposer;
public CachedEvaluation(Consumer<? super CachedEvaluation> disposer) {
this.disposer = disposer;
}
@Override
public void dispose() {
disposer.accept(this);
}
}

View File

@ -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<? super CachedChange> 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);
}

View File

@ -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<? super CachedChange> disposer) {
super(disposer);
}
public <T extends EntityData> void set(T entity, StateChange<T> 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<EntityData>) 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;
}
}

View File

@ -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<? super CachedChange> disposer) {
super(disposer, "Core:SetBlock");
}
public void initialize(Vec3i blockInWorld, BlockData block) {
if (this.block != null)
throw new IllegalStateException("Payload is not null. Current: " + this.block + "; requested: " + block);
this.blockInWorld.set(blockInWorld.x, blockInWorld.y, blockInWorld.z);
this.block = block;
}
@Override
protected void affectCommon(WorldData world) {
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;
}
}

View File

@ -0,0 +1,6 @@
package ru.windcorp.progressia.server.world.tasks;
@FunctionalInterface
public interface StateChange<T> {
void change(T object);
}

View File

@ -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<? super CachedEvaluation> 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);
}
}

View File

@ -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<TickerTask> 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 <T extends EntityData> void changeEntity(
T entity, StateChange<T> 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);
}
}

View File

@ -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.
* <p>
* 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);
}
}

View File

@ -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.
}
}

View File

@ -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.
* <p>
* 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);
}
}

View File

@ -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<TickerTask> 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<TickerTask> 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();
}
}

View File

@ -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<Change> pendingChanges = new ArrayList<>(INITIAL_QUEUE_SIZE);
// Synchronized manually
private final Collection<Evaluation> 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<TickerTask> 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<TickerTask> toDispose = new ArrayList<>(INITIAL_QUEUE_SIZE);
private final Collection<Ticker> tickers;
private final Collection<Thread> 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<Ticker> 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<Thread> 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<? extends TickerTask> tasks,
String stageName
) throws InterruptedException {
if (!toDispose.isEmpty())
throw new IllegalStateException("toDispose is not empty: " + toDispose);
Collection<TickerTask> toDispose = this.toDispose;
startPassStage(tasks, toDispose, stageName);
sync();
dispose(toDispose);
}
private void dispose(Collection<TickerTask> toDispose) {
toDispose.forEach(TickerTask::dispose);
toDispose.clear();
}
private synchronized void startPassStage(
Collection<? extends TickerTask> tasks,
Collection<TickerTask> 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<TickerTask> selectedTasks = cachedTransferList;
ticker.requestWork(selectTasks(ticker, tasks, selectedTasks));
selectedTasks.clear();
}
toDispose.addAll(tasks);
tasks.clear();
logger.debug("Stage started");
}
private Collection<TickerTask> selectTasks(
Ticker ticker,
Collection<? extends TickerTask> tasks,
Collection<TickerTask> 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
);
}
}

View File

@ -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);
}

View File

@ -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());
}
}

View File

@ -1,17 +1,9 @@
package ru.windcorp.progressia.server.world.tile; package ru.windcorp.progressia.server.world.tile;
import java.util.List;
import glm.vec._3.i.Vec3i; 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.block.BlockFace;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.server.Server; 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.WorldLogic;
import ru.windcorp.progressia.server.world.block.BlockLogic;
public class ForwardingTileTickContext implements TileTickContext { public class ForwardingTileTickContext implements TileTickContext {
@ -29,16 +21,6 @@ public class ForwardingTileTickContext implements TileTickContext {
this.parent = parent; this.parent = parent;
} }
@Override
public ChunkLogic getChunk() {
return parent.getChunk();
}
@Override
public ChunkData getChunkData() {
return parent.getChunkData();
}
@Override @Override
public double getTickLength() { public double getTickLength() {
return parent.getTickLength(); return parent.getTickLength();
@ -55,78 +37,23 @@ public class ForwardingTileTickContext implements TileTickContext {
} }
@Override @Override
public WorldData getWorldData() { public Vec3i getCurrentBlockInWorld() {
return parent.getWorldData(); return parent.getCurrentBlockInWorld();
} }
@Override @Override
public void requestBlockTick(Vec3i blockInWorld) { public Vec3i getCounterBlockInWorld() {
parent.requestBlockTick(blockInWorld); return parent.getCounterBlockInWorld();
} }
@Override @Override
public void requestTileTick(Vec3i blockInWorld, BlockFace face, int layer) { public BlockFace getCurrentFace() {
parent.requestTileTick(blockInWorld, face, layer); return parent.getCurrentFace();
} }
@Override @Override
public Vec3i getCoords() { public int getCurrentLayer() {
return parent.getCoords(); return parent.getCurrentLayer();
}
@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<TileLogic> getTiles() {
return parent.getTiles();
}
@Override
public List<TileLogic> getTilesOrNull() {
return parent.getTilesOrNull();
}
@Override
public List<TileData> getTileDataList() {
return parent.getTileDataList();
}
@Override
public List<TileData> getTileDataListOrNull() {
return parent.getTileDataListOrNull();
}
@Override
public BlockLogic getBlock() {
return parent.getBlock();
}
@Override
public BlockData getBlockData() {
return parent.getBlockData();
} }
} }

View File

@ -1,10 +1,8 @@
package ru.windcorp.progressia.server.world.tile; package ru.windcorp.progressia.server.world.tile;
import ru.windcorp.progressia.server.world.Changer;
public interface TickableTile { public interface TickableTile {
void tick(TileTickContext context, Changer changer); void tick(TileTickContext context);
default boolean doesTickRegularly(TileTickContext context) { default boolean doesTickRegularly(TileTickContext context) {
return false; return false;

View File

@ -10,7 +10,7 @@ public class TileLogic extends Namespaced {
} }
public boolean canOccupyFace(TileTickContext context) { public boolean canOccupyFace(TileTickContext context) {
return canOccupyFace(context.getFace()); return canOccupyFace(context.getCurrentFace());
} }
public boolean canOccupyFace(BlockFace face) { public boolean canOccupyFace(BlockFace face) {

View File

@ -3,69 +3,158 @@ package ru.windcorp.progressia.server.world.tile;
import java.util.List; import java.util.List;
import glm.vec._3.i.Vec3i; 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.BlockData;
import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.tile.TileData; 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.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. * Returns the current world coordinates.
* @return the world coordinates of the tile being ticked * @return the world coordinates of the tile being ticked
*/ */
Vec3i getCoords(); Vec3i getCurrentBlockInWorld();
/** /**
* Returns the current chunk coordinates. * Returns the counter world coordinates.
* @return the chunk coordinates of the tile being ticked * @return the world coordinates of the tile being ticked
*/ */
Vec3i getChunkCoords(); Vec3i getCounterBlockInWorld();
/** /**
* Returns the current block face. This face is always * Returns the current block face.
* {@linkplain BlockFace#isPrimary() primary}.
* @return the block face that the tile being ticked occupies * @return the block face that the tile being ticked occupies
*/ */
BlockFace getFace(); BlockFace getCurrentFace();
/** /**
* Returns the current layer. * Returns the current layer.
* @return the layer that the tile being ticked occupies in the tile stack * @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() { default TileLogic getTile() {
return getTiles().get(getLayer()); return getTiles().get(getCurrentLayer());
} }
default TileData getTileData() { default TileData getTileData() {
return getTileDataList().get(getLayer()); return getTileDataList().get(getCurrentLayer());
} }
default List<TileLogic> getTiles() { default List<TileLogic> getTiles() {
return getChunk().getTiles(getChunkCoords(), getFace()); Vec3i blockInChunk = Vectors.grab3i();
Coordinates.convertInWorldToInChunk(getCurrentBlockInWorld(), blockInChunk);
List<TileLogic> result = getCurrentChunk().getTiles(blockInChunk, getCurrentFace());
Vectors.release(blockInChunk);
return result;
} }
default List<TileLogic> getTilesOrNull() { default List<TileLogic> getTilesOrNull() {
return getChunk().getTilesOrNull(getChunkCoords(), getFace()); Vec3i blockInChunk = Vectors.grab3i();
Coordinates.convertInWorldToInChunk(getCurrentBlockInWorld(), blockInChunk);
List<TileLogic> result = getCurrentChunk().getTilesOrNull(blockInChunk, getCurrentFace());
Vectors.release(blockInChunk);
return result;
} }
default List<TileData> getTileDataList() { default List<TileData> getTileDataList() {
return getChunkData().getTiles(getChunkCoords(), getFace()); Vec3i blockInChunk = Vectors.grab3i();
Coordinates.convertInWorldToInChunk(getCurrentBlockInWorld(), blockInChunk);
List<TileData> result = getCurrentChunkData().getTiles(blockInChunk, getCurrentFace());
Vectors.release(blockInChunk);
return result;
} }
default List<TileData> getTileDataListOrNull() { default List<TileData> getTileDataListOrNull() {
return getChunkData().getTilesOrNull(getChunkCoords(), getFace()); Vec3i blockInChunk = Vectors.grab3i();
Coordinates.convertInWorldToInChunk(getCurrentBlockInWorld(), blockInChunk);
List<TileData> 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() { default ChunkData getCurrentChunkData() {
return getChunkData().getBlock(getChunkCoords()); 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);
} }
} }

View File

@ -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);
}

View File

@ -0,0 +1,7 @@
package ru.windcorp.progressia.server.world.tile;
public interface UpdateableTile {
void update(TileTickContext context);
}

View File

@ -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;
}
}

View File

@ -54,7 +54,7 @@ public class TestContent {
} }
}); });
register(new BlockRenderNone("Test:Air")); register(new BlockRenderNone("Test:Air"));
register(new BlockLogic("Test:Air")); register(new TestBlockLogicAir("Test:Air"));
register(new BlockData("Test:Dirt")); register(new BlockData("Test:Dirt"));
register(new BlockRenderOpaqueCube("Test:Dirt", getBlockTexture("dirt"))); register(new BlockRenderOpaqueCube("Test:Dirt", getBlockTexture("dirt")));
@ -76,19 +76,19 @@ public class TestContent {
private static void registerTiles() { private static void registerTiles() {
register(new TileData("Test:Grass")); register(new TileData("Test:Grass"));
register(new TileRenderGrass("Test:Grass", getTileTexture("grass_top"), getTileTexture("grass_side"))); 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 TileData("Test:Stones"));
register(new TileRenderSimple("Test:Stones", getTileTexture("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 TileData("Test:YellowFlowers"));
register(new TileRenderSimple("Test:YellowFlowers", getTileTexture("yellow_flowers"))); 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 TileData("Test:Sand"));
register(new TileRenderSimple("Test:Sand", getTileTexture("sand"))); register(new TileRenderSimple("Test:Sand", getTileTexture("sand")));
register(new TileLogic("Test:Sand")); register(new EdgeTileLogic("Test:Sand"));
} }
private static void registerEntities() { private static void registerEntities() {
@ -125,7 +125,7 @@ public class TestContent {
block = BlockDataRegistry.getInstance().get("Test:Stone"); block = BlockDataRegistry.getInstance().get("Test:Stone");
} }
server.getAdHocChanger().setBlock(z000, block); server.getWorldAccessor().setBlock(z000, block);
})); }));
data.register("Test:BreakBlock", ControlBreakBlockData::new); 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) { private static void onBlockBreakReceived(Server server, PacketControl packet, ru.windcorp.progressia.server.comms.Client client) {
Vec3i blockInWorld = ((ControlBreakBlockData) packet.getControl()).getBlockInWorld(); 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) { 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) { private static void onBlockPlaceReceived(Server server, PacketControl packet, ru.windcorp.progressia.server.comms.Client client) {
Vec3i blockInWorld = ((ControlPlaceBlockData) packet.getControl()).getBlockInWorld(); Vec3i blockInWorld = ((ControlPlaceBlockData) packet.getControl()).getBlockInWorld();
if (server.getWorld().getData().getChunkByBlock(blockInWorld) == null) return; 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"));
} }
} }

View File

@ -1,7 +1,6 @@
package ru.windcorp.progressia.test; package ru.windcorp.progressia.test;
import ru.windcorp.progressia.common.world.entity.EntityData; 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.TickContext;
import ru.windcorp.progressia.server.world.entity.EntityLogic; import ru.windcorp.progressia.server.world.entity.EntityLogic;
@ -12,13 +11,13 @@ public class TestEntityLogicStatie extends EntityLogic {
} }
@Override @Override
public void tick(EntityData entity, TickContext context, Changer changer) { public void tick(EntityData entity, TickContext context) {
super.tick(entity, context, changer); super.tick(entity, context);
TestEntityDataStatie statie = (TestEntityDataStatie) entity; TestEntityDataStatie statie = (TestEntityDataStatie) entity;
int size = (int) (18 + 6 * Math.sin(entity.getAge())); 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));
} }
} }

View File

@ -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();
}
}
}

View File

@ -8,12 +8,12 @@
<Appenders> <Appenders>
<Console name="Console" target="SYSTEM_OUT"> <Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%-20t] %-5level %-32logger{32} > %msg%n"/>
</Console> </Console>
<RollingFile name="FileLog" fileName="${APP_LOG_ROOT}/game.log" <RollingFile name="FileLog" fileName="${APP_LOG_ROOT}/game.log"
filePattern="${APP_LOG_ROOT}/game-%d{yyyy-MM-dd}-%i.log"> filePattern="${APP_LOG_ROOT}/game-%d{yyyy-MM-dd}-%i.log">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%-20t] %-5level %-32logger{32} > %msg%n"/>
<Policies> <Policies>
<SizeBasedTriggeringPolicy size="18MB" /> <SizeBasedTriggeringPolicy size="18MB" />
</Policies> </Policies>
@ -21,6 +21,12 @@
</Appenders> </Appenders>
<Loggers> <Loggers>
<!-- Uncomment to enable Ticker debugging
<Logger name="Ticker Coordinator" level="DEBUG" />
<Logger name="Ticker 0" level="DEBUG" />
-->
<Root level="info"> <Root level="info">
<AppenderRef ref="FileLog" /> <AppenderRef ref="FileLog" />
<AppenderRef ref="Console" /> <AppenderRef ref="Console" />