Added server and reorganized client

- Added server
  - Managed with ServerState
- Added BlockLogic (no TileLogic yet)
- Added ticking
- Added server-client communication API (only local implemented, no net)
  - Added controls system (not fully implemented)
- Added Client class
  - Contains most game-related references for client: WorldRender,
Camera, ...
  - Managed with ClientState

fjckign finally~
This commit is contained in:
OLEGSHA 2020-08-30 19:30:32 +03:00
parent e8f3177266
commit 30e61cf33f
56 changed files with 2045 additions and 174 deletions

View File

@ -0,0 +1,41 @@
package ru.windcorp.progressia.client;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.comms.DefaultClientCommsListener;
import ru.windcorp.progressia.client.comms.ServerCommsChannel;
import ru.windcorp.progressia.client.graphics.world.Camera;
import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.common.world.WorldData;
public class Client {
private final WorldRender world;
private final Camera camera = new Camera(
new Vec3(-6, -6, 20),
(float) Math.toRadians(-40), (float) Math.toRadians(-45),
(float) Math.toRadians(70)
);
private final ServerCommsChannel comms;
public Client(WorldData world, ServerCommsChannel comms) {
this.world = new WorldRender(world);
this.comms = comms;
comms.addListener(new DefaultClientCommsListener(this));
}
public WorldRender getWorld() {
return world;
}
public Camera getCamera() {
return camera;
}
public ServerCommsChannel getComms() {
return comms;
}
}

View File

@ -18,20 +18,15 @@
package ru.windcorp.progressia.client;
import ru.windcorp.progressia.Proxy;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
import ru.windcorp.progressia.client.graphics.backend.RenderTaskQueue;
import ru.windcorp.progressia.client.graphics.flat.FlatRenderProgram;
import ru.windcorp.progressia.client.graphics.flat.LayerTestUI;
import ru.windcorp.progressia.client.graphics.font.GNUUnifontLoader;
import ru.windcorp.progressia.client.graphics.font.Typefaces;
import ru.windcorp.progressia.client.graphics.gui.LayerTestGUI;
import ru.windcorp.progressia.client.graphics.texture.Atlases;
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.renders.BlockRenders;
import ru.windcorp.progressia.client.world.renders.TileRenders;
import ru.windcorp.progressia.common.resource.ResourceManager;
import ru.windcorp.progressia.server.ServerState;
public class ClientProxy implements Proxy {
@ -47,13 +42,12 @@ public class ClientProxy implements Proxy {
e.printStackTrace();
}
BlockRenders.registerTest();
TileRenders.registerTest();
TestContent.registerContent();
Atlases.loadAllAtlases();
GUI.addBottomLayer(new LayerWorld());
GUI.addTopLayer(new LayerTestUI());
GUI.addTopLayer(new LayerTestGUI());
ServerState.startServer();
ClientState.connectToLocalServer();
}
}

View File

@ -0,0 +1,40 @@
package ru.windcorp.progressia.client;
import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.flat.LayerTestUI;
import ru.windcorp.progressia.client.graphics.gui.LayerTestGUI;
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.server.ServerState;
public class ClientState {
private static Client instance;
public static Client getInstance() {
return instance;
}
public static void setInstance(Client instance) {
ClientState.instance = instance;
}
public static void connectToLocalServer() {
WorldData world = new WorldData();
Client client = new Client(world, new LocalServerCommsChannel(
ServerState.getInstance()
));
setInstance(client);
GUI.addBottomLayer(new LayerWorld(client));
GUI.addTopLayer(new LayerTestUI());
GUI.addTopLayer(new LayerTestGUI());
}
private ClientState() {}
}

View File

@ -0,0 +1,101 @@
package ru.windcorp.progressia.client;
import static ru.windcorp.progressia.client.world.renders.BlockRenders.register;
import static ru.windcorp.progressia.client.world.renders.BlockRenders.getBlockTexture;
import static ru.windcorp.progressia.client.world.renders.TileRenders.register;
import static ru.windcorp.progressia.client.world.renders.TileRenders.getTileTexture;
import static ru.windcorp.progressia.common.block.BlockDataRegistry.register;
import static ru.windcorp.progressia.common.block.TileDataRegistry.register;
import static ru.windcorp.progressia.server.block.BlockLogicRegistry.register;
import org.lwjgl.glfw.GLFW;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.comms.controls.ControlTriggerOnKeyPress;
import ru.windcorp.progressia.client.comms.controls.ControlTriggerRegistry;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.client.world.renders.*;
import ru.windcorp.progressia.common.block.*;
import ru.windcorp.progressia.common.comms.controls.ControlData;
import ru.windcorp.progressia.common.comms.controls.ControlDataRegistry;
import ru.windcorp.progressia.common.comms.controls.PacketControl;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.block.*;
import ru.windcorp.progressia.server.comms.Client;
import ru.windcorp.progressia.server.comms.controls.ControlLogic;
import ru.windcorp.progressia.server.comms.controls.ControlLogicRegistry;
public class TestContent {
public static void registerContent() {
registerWorldContent();
regsiterControls();
}
private static void registerWorldContent() {
registerBlocks();
registerTiles();
}
private static void registerBlocks() {
register(new BlockData("Test", "Air"));
register(new BlockRenderNone("Test", "Air"));
register(new BlockLogic("Test", "Air"));
register(new BlockData("Test", "Dirt"));
register(new BlockRenderOpaqueCube("Test", "Dirt", getBlockTexture("dirt")));
register(new BlockLogic("Test", "Dirt"));
register(new BlockData("Test", "Stone"));
register(new BlockRenderOpaqueCube("Test", "Stone", getBlockTexture("stone")));
register(new BlockLogic("Test", "Stone"));
register(new BlockData("Test", "Compass"));
register(new BlockRenderOpaqueCube("Test", "Compass", getBlockTexture("compass")));
register(new BlockLogic("Test", "Compass"));
register(new BlockData("Test", "Glass"));
register(new BlockRenderTransparentCube("Test", "Glass", getBlockTexture("glass_clear")));
register(new BlockLogic("Test", "Glass"));
}
private static void registerTiles() {
register(new TileData("Test", "Grass"));
register(new TileRenderGrass("Test", "Grass", getTileTexture("grass_top"), getTileTexture("grass_side")));
register(new TileData("Test", "Stones"));
register(new TileRenderSimple("Test", "Stones", getTileTexture("stones")));
register(new TileData("Test", "YellowFlowers"));
register(new TileRenderSimple("Test", "YellowFlowers", getTileTexture("yellow_flowers")));
register(new TileData("Test", "Sand"));
register(new TileRenderSimple("Test", "Sand", getTileTexture("sand")));
}
private static void regsiterControls() {
ControlDataRegistry.getInstance().register(new ControlData("Test", "Switch000"));
ControlTriggerRegistry.getInstance().register(new ControlTriggerOnKeyPress("Test", "Switch000", new KeyMatcher(GLFW.GLFW_KEY_G, new int[0], 0)::matches));
ControlLogicRegistry.getInstance().register(new ControlLogic("Test", "Switch000") {
@Override
public void apply(Server server, PacketControl packet, Client client) {
Vec3i z000 = new Vec3i(0, 0, 0);
ChunkData data = server.getWorld().getChunk(z000).getData();
BlockData block;
if (data.getBlock(z000).getId().equals("Test:Stone")) {
block = BlockDataRegistry.get("Test:Glass");
} else {
block = BlockDataRegistry.get("Test:Stone");
}
server.getAdHocChanger().setBlock(z000, block);
}
});
}
}

View File

@ -0,0 +1,43 @@
package ru.windcorp.progressia.client.comms;
import java.io.IOException;
import ru.windcorp.progressia.client.Client;
import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.common.comms.CommsListener;
import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.common.comms.packets.PacketWorldChange;
public class DefaultClientCommsListener implements CommsListener {
private final Client client;
public DefaultClientCommsListener(Client client) {
this.client = client;
}
@Override
public void onPacketReceived(Packet packet) {
if (packet instanceof PacketWorldChange) {
((PacketWorldChange) packet).apply(
getClient().getWorld().getData()
);
tmp_reassembleWorld();
}
}
private void tmp_reassembleWorld() {
getClient().getWorld().getChunks().forEach(ChunkRender::markForUpdate);
}
@Override
public void onIOError(IOException reason) {
// TODO implement
}
public Client getClient() {
return client;
}
}

View File

@ -0,0 +1,11 @@
package ru.windcorp.progressia.client.comms;
import ru.windcorp.progressia.common.comms.CommsChannel;
public abstract class ServerCommsChannel extends CommsChannel {
public ServerCommsChannel(Role... roles) {
super(roles);
}
}

View File

@ -0,0 +1,11 @@
package ru.windcorp.progressia.client.comms.controls;
import ru.windcorp.progressia.common.util.Namespaced;
public abstract class ControlTrigger extends Namespaced {
public ControlTrigger(String namespace, String name) {
super(namespace, name);
}
}

View File

@ -0,0 +1,14 @@
package ru.windcorp.progressia.client.comms.controls;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.common.comms.controls.PacketControl;
public abstract class ControlTriggerInputBased extends ControlTrigger {
public ControlTriggerInputBased(String namespace, String name) {
super(namespace, name);
}
public abstract PacketControl onInputEvent(InputEvent event);
}

View File

@ -0,0 +1,39 @@
package ru.windcorp.progressia.client.comms.controls;
import java.util.function.Predicate;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.common.comms.controls.ControlDataRegistry;
import ru.windcorp.progressia.common.comms.controls.PacketControl;
public class ControlTriggerOnKeyPress extends ControlTriggerInputBased {
private final Predicate<KeyEvent> predicate;
private final PacketControl packet;
public ControlTriggerOnKeyPress(
String namespace, String name,
Predicate<KeyEvent> predicate
) {
super(namespace, name);
this.predicate = predicate;
this.packet = new PacketControl(
getNamespace(), "ControlKeyPress" + getName(),
ControlDataRegistry.getInstance().get(getId())
);
}
@Override
public PacketControl onInputEvent(InputEvent event) {
if (!(event instanceof KeyEvent)) return null;
KeyEvent keyEvent = (KeyEvent) event;
if (!keyEvent.isPress()) return null;
if (!predicate.test(keyEvent)) return null;
return packet;
}
}

View File

@ -0,0 +1,14 @@
package ru.windcorp.progressia.client.comms.controls;
import ru.windcorp.progressia.common.util.NamespacedRegistry;
public class ControlTriggerRegistry extends NamespacedRegistry<ControlTrigger> {
private static final ControlTriggerRegistry INSTANCE =
new ControlTriggerRegistry();
public static ControlTriggerRegistry getInstance() {
return INSTANCE;
}
}

View File

@ -0,0 +1,33 @@
package ru.windcorp.progressia.client.comms.controls;
import ru.windcorp.progressia.client.Client;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.common.comms.packets.Packet;
public class InputBasedControls {
private final Client client;
private final ControlTriggerInputBased[] controls;
public InputBasedControls(Client client) {
this.client = client;
this.controls = ControlTriggerRegistry.getInstance().values().stream()
.filter(ControlTriggerInputBased.class::isInstance)
.toArray(ControlTriggerInputBased[]::new);
}
public void handleInput(Input input) {
for (ControlTriggerInputBased c : controls) {
Packet packet = c.onInputEvent(input.getEvent());
if (packet != null) {
input.consume();
client.getComms().sendPacket(packet);
break;
}
}
}
}

View File

@ -0,0 +1,33 @@
package ru.windcorp.progressia.client.comms.localhost;
import java.io.IOException;
import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.server.comms.Client;
public class LocalClient extends Client {
private final LocalServerCommsChannel serverComms;
public LocalClient(int id, LocalServerCommsChannel serverComms) {
super(id, Role.GAME, Role.CHAT);
setState(State.CONNECTED);
this.serverComms = serverComms;
}
@Override
protected void doSendPacket(Packet packet) throws IOException {
this.serverComms.relayPacketToClient(packet);
}
public void relayPacketToServer(Packet packet) {
onPacketReceived(packet);
}
@Override
public void disconnect() {
// Do nothing
}
}

View File

@ -0,0 +1,37 @@
package ru.windcorp.progressia.client.comms.localhost;
import ru.windcorp.progressia.client.comms.ServerCommsChannel;
import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.server.Server;
public class LocalServerCommsChannel extends ServerCommsChannel {
private final LocalClient localClient;
public LocalServerCommsChannel(Server server) {
super(Role.GAME, Role.CHAT);
setState(State.CONNECTED);
this.localClient = new LocalClient(
server.getClientManager().grabClientId(),
this
);
server.getClientManager().addClient(localClient);
}
@Override
protected void doSendPacket(Packet packet) {
localClient.relayPacketToServer(packet);
}
public void relayPacketToClient(Packet packet) {
onPacketReceived(packet);
}
@Override
public void disconnect() {
// Do nothing
}
}

View File

@ -17,91 +17,30 @@
*******************************************************************************/
package ru.windcorp.progressia.client.graphics.backend;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import ru.windcorp.jputil.functions.ThrowingRunnable;
import ru.windcorp.progressia.common.util.TaskQueue;
public class RenderTaskQueue {
private static final Queue<Runnable> QUEUE = new ConcurrentLinkedQueue<>();
private RenderTaskQueue() {}
private static final TaskQueue HANDLER =
new TaskQueue(GraphicsInterface::isRenderThread);
public static void invokeLater(Runnable task) {
QUEUE.add(task);
HANDLER.invokeLater(task);
}
public static void invokeNow(Runnable task) {
if (GraphicsInterface.isRenderThread()) {
task.run();
} else {
invokeLater(task);
}
HANDLER.invokeNow(task);
}
private static final Object WAIT_AND_INVOKE_MONITOR = new Object();
@SuppressWarnings("unchecked")
public static <E extends Exception> void waitAndInvoke(
ThrowingRunnable<E> task
) throws InterruptedException, E {
if (GraphicsInterface.isRenderThread()) {
task.run();
return;
}
final AtomicBoolean flag =
new AtomicBoolean(false);
final AtomicReference<Throwable> thrownContainer =
new AtomicReference<>(null);
invokeLater(() -> {
try {
task.run();
} catch (Throwable t) {
thrownContainer.set(t);
}
flag.set(true);
synchronized (WAIT_AND_INVOKE_MONITOR) {
WAIT_AND_INVOKE_MONITOR.notifyAll();
}
});
while (!flag.get()) {
synchronized (WAIT_AND_INVOKE_MONITOR) {
WAIT_AND_INVOKE_MONITOR.wait();
}
}
Throwable thrown = thrownContainer.get();
if (thrown != null) {
if (thrown instanceof RuntimeException) {
throw (RuntimeException) thrown;
}
if (thrown instanceof Error) {
throw (Error) thrown;
}
throw (E) thrown; // Guaranteed
}
HANDLER.waitAndInvoke(task);
}
public static void runTasks() {
Iterator<Runnable> tasks = QUEUE.iterator();
while (tasks.hasNext()) {
tasks.next().run();
tasks.remove();
}
HANDLER.runTasks();
}
}

View File

@ -43,6 +43,8 @@ class RenderThread extends Thread {
waitForFrame();
GraphicsBackend.endFrame();
}
System.exit(0);
}
private void render() {

View File

@ -22,6 +22,7 @@ import org.lwjgl.glfw.GLFW;
import com.google.common.eventbus.Subscribe;
import glm.mat._4.Mat4;
import ru.windcorp.progressia.client.ClientState;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
@ -29,7 +30,6 @@ import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.client.graphics.model.LambdaModel;
import ru.windcorp.progressia.client.graphics.texture.SimpleTextures;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
public class LayerTestUI extends AssembledFlatLayer {
@ -73,7 +73,7 @@ public class LayerTestUI extends AssembledFlatLayer {
target.addCustomRenderer(new LambdaModel(LambdaModel.lambdaBuilder()
.addDynamicPart(
target.createRectagle(0, 0, texSize, texSize, 0xFFFFFF, compassFg),
mat -> mat.translate(texSize/2, texSize/2, 0).rotateZ(LayerWorld.tmp_the_camera.getYaw()).translate(-texSize/2, -texSize/2, 0)
mat -> mat.translate(texSize/2, texSize/2, 0).rotateZ(ClientState.getInstance().getCamera().getYaw()).translate(-texSize/2, -texSize/2, 0)
)
));
target.popTransform();

View File

@ -21,7 +21,8 @@ import org.lwjgl.glfw.GLFW;
import glm.mat._3.Mat3;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.Client;
import ru.windcorp.progressia.client.comms.controls.InputBasedControls;
import ru.windcorp.progressia.client.graphics.Layer;
import ru.windcorp.progressia.client.graphics.backend.GraphicsBackend;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
@ -29,22 +30,10 @@ import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.common.block.BlockDataRegistry;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.WorldData;
public class LayerWorld extends Layer {
public static Camera tmp_the_camera;
private final Camera camera = new Camera(
new Vec3(-6, -6, 20),
(float) Math.toRadians(-40), (float) Math.toRadians(-45),
(float) Math.toRadians(70)
);
private final Vec3 velocity = new Vec3();
private final Mat3 angMat = new Mat3();
@ -55,11 +44,13 @@ public class LayerWorld extends Layer {
private final WorldRenderHelper helper = new WorldRenderHelper();
private final WorldRender world = new WorldRender(new WorldData());
private final Client client;
private final InputBasedControls inputBasedControls;
public LayerWorld() {
public LayerWorld(Client client) {
super("World");
tmp_the_camera = camera;
this.client = client;
this.inputBasedControls = new InputBasedControls(client);
}
@Override
@ -75,11 +66,11 @@ public class LayerWorld extends Layer {
@Override
protected void doRender() {
camera.apply(helper);
client.getCamera().apply(helper);
renderWorld();
helper.reset();
angMat.set().rotateZ(-camera.getYaw());
angMat.set().rotateZ(-client.getCamera().getYaw());
Vec3 movement = Vectors.grab3();
@ -97,13 +88,17 @@ public class LayerWorld extends Layer {
Vec3 velCopy = Vectors.grab3().set(velocity);
velCopy.mul((float) (GraphicsInterface.getFrameLength() * 60));
camera.move(velCopy);
client.getCamera().move(velCopy);
Vectors.release(velCopy);
if (GraphicsBackend.getFramesRendered() % 60 == 0) {
System.out.println(GraphicsInterface.getFPS());
}
}
private void renderWorld() {
world.render(helper);
this.client.getWorld().render(helper);
}
@Override
@ -113,22 +108,23 @@ public class LayerWorld extends Layer {
InputEvent event = input.getEvent();
if (event instanceof KeyEvent) {
onKeyEvent((KeyEvent) event);
if (onKeyEvent((KeyEvent) event)) {
input.consume();
}
} else if (event instanceof CursorMoveEvent) {
onMouseMoved((CursorMoveEvent) event);
input.consume();
}
}
public Camera getCamera() {
return camera;
if (!input.isConsumed()) {
inputBasedControls.handleInput(input);
}
}
private boolean flag = true;
private void onKeyEvent(KeyEvent event) {
if (event.isRepeat()) return;
private boolean onKeyEvent(KeyEvent event) {
if (event.isRepeat()) return false;
int multiplier = event.isPress() ? 1 : -1;
@ -153,7 +149,7 @@ public class LayerWorld extends Layer {
break;
case GLFW.GLFW_KEY_ESCAPE:
if (!event.isPress()) return;
if (!event.isPress()) return false;
if (flag) {
GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL);
@ -164,25 +160,11 @@ public class LayerWorld extends Layer {
flag = !flag;
break;
case GLFW.GLFW_KEY_G:
if (!event.isPress()) return;
Vec3i pos = Vectors.grab3i().set(0, 0, 0);
Vec3i chunkPos = Vectors.grab3i().set(0, 0, 0);
ChunkData chunk = world.getData().getChunk(chunkPos);
if (chunk.getBlock(pos).getId().equals("Test:Stone")) {
chunk.setBlock(pos, BlockDataRegistry.get("Test:Glass"));
} else {
chunk.setBlock(pos, BlockDataRegistry.get("Test:Stone"));
default:
return false;
}
world.getChunk(chunkPos).markForUpdate();
Vectors.release(pos);
Vectors.release(chunkPos);
break;
}
return true;
}
private void onMouseMoved(CursorMoveEvent event) {
@ -191,7 +173,7 @@ public class LayerWorld extends Layer {
final float yawScale = 0.002f;
final float pitchScale = yawScale;
camera.turn(
client.getCamera().turn(
(float) (event.getChangeY() * pitchScale),
(float) (event.getChangeX() * yawScale)
);

View File

@ -34,23 +34,8 @@ public class BlockRenders {
private static final AtlasGroup BLOCKS_ATLAS_GROUP =
new AtlasGroup("Blocks", 1 << 12);
private static Texture dirt = getTexture("dirt");
private static Texture stone = getTexture("stone");
private static Texture glass = getTexture("glass_clear");
private static Texture compass = getTexture("compass");
private BlockRenders() {}
public static void registerTest() {
register(new BlockRenderOpaqueCube("Test", "Dirt", dirt, dirt, dirt, dirt, dirt, dirt));
register(new BlockRenderOpaqueCube("Test", "Stone", stone, stone, stone, stone, stone, stone));
register(new BlockRenderOpaqueCube("Test", "Compass", compass, compass, getTexture("side_north"), getTexture("side_south"), getTexture("side_east"), getTexture("side_west")));
register(new BlockRenderNone("Test", "Air"));
register(new BlockRenderTransparentCube("Test", "Glass", glass, glass, glass, glass, glass, glass));
}
public static BlockRender get(String name) {
return BLOCK_RENDERS.get(name);
}
@ -59,7 +44,7 @@ public class BlockRenders {
BLOCK_RENDERS.put(blockRender.getId(), blockRender);
}
public static Texture getTexture(String name) {
public static Texture getBlockTexture(String name) {
return new SimpleTexture(
Atlases.getSprite(
ResourceManager.getTextureResource("blocks/" + name),

View File

@ -36,14 +36,6 @@ public class TileRenders {
private TileRenders() {}
public static void registerTest() {
register(new TileRenderGrass("Test", "Grass", getTexture("grass_top"), getTexture("grass_side")));
register(new TileRenderSimple("Test", "Stones", getTexture("stones")));
register(new TileRenderSimple("Test", "YellowFlowers", getTexture("yellow_flowers")));
register(new TileRenderSimple("Test", "Sand", getTexture("sand")));
}
public static TileRender get(String name) {
return TILE_RENDERS.get(name);
}
@ -52,7 +44,7 @@ public class TileRenders {
TILE_RENDERS.put(tileRender.getId(), tileRender);
}
public static Texture getTexture(String name) {
public static Texture getTileTexture(String name) {
return new SimpleTexture(
Atlases.getSprite(
ResourceManager.getTextureResource("tiles/" + name),

View File

@ -24,13 +24,6 @@ public class BlockDataRegistry {
private static final Map<String, BlockData> REGISTRY = new HashMap<>();
static {
register(new BlockData("Test", "Dirt"));
register(new BlockData("Test", "Stone"));
register(new BlockData("Test", "Air"));
register(new BlockData("Test", "Glass"));
}
public static BlockData get(String name) {
return REGISTRY.get(name);
}

View File

@ -24,13 +24,6 @@ public class TileDataRegistry {
private static final Map<String, TileData> REGISTRY = new HashMap<>();
static {
register(new TileData("Test", "Grass"));
register(new TileData("Test", "Stones"));
register(new TileData("Test", "YellowFlowers"));
register(new TileData("Test", "Sand"));
}
public static TileData get(String name) {
return REGISTRY.get(name);
}

View File

@ -0,0 +1,136 @@
package ru.windcorp.progressia.common.comms;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import com.google.common.collect.Sets;
import ru.windcorp.progressia.common.comms.packets.Packet;
public abstract class CommsChannel {
public static enum State {
/**
* Client is currently establishing connection.
*/
CONNECTING,
/**
* Client is ready to receive and send packets.
*/
CONNECTED,
/**
* Client is being disconnected.
*/
DISCONNECTING,
/**
* Communication is not possible. The client may have been disconnected
* after connecting or may have never connected.
*/
DISCONNECTED
}
public static enum Role {
GAME,
CHAT,
RCON
// TODO create role for that thingy that only connects to get server status
}
private State state = State.CONNECTING;
protected final Set<Role> roles;
private final Collection<CommsListener> listeners =
Collections.synchronizedCollection(new ArrayList<>());
public CommsChannel(Role... roles) {
this.roles = Sets.immutableEnumSet(Arrays.asList(roles));
}
protected abstract void doSendPacket(Packet packet) throws IOException;
private synchronized void sendPacket(
Packet packet,
State expectedState, String errorMessage
) {
if (getState() != expectedState) {
throw new IllegalStateException(
String.format(errorMessage, this, getState())
);
}
try {
doSendPacket(packet);
} catch (IOException e) {
onIOError(e, "Could not send packet");
}
}
public synchronized void sendPacket(Packet packet) {
sendPacket(
packet,
State.CONNECTED,
"Client %s is in state %s and cannot receive packets normally"
);
}
public synchronized void sendConnectingPacket(Packet packet) {
sendPacket(
packet,
State.CONNECTING,
"Client %s is in state %s and is no longer connecting"
);
}
public synchronized void sendDisconnectingPacket(Packet packet) {
sendPacket(
packet,
State.CONNECTING,
"Client %s is in state %s and is no longer disconnecting"
);
}
public abstract void disconnect();
protected void onPacketReceived(Packet packet) {
listeners.forEach(l -> l.onPacketReceived(packet));
}
public void addListener(CommsListener listener) {
listeners.add(listener);
}
public void removeListener(CommsListener listener) {
listeners.remove(listener);
}
protected void onIOError(IOException e, String string) {
// TODO implement
e.printStackTrace();
listeners.forEach(l -> l.onIOError(e));
}
public synchronized State getState() {
return state;
}
public Set<Role> getRoles() {
return roles;
}
public boolean isReady() {
return getState() == State.CONNECTED;
}
public synchronized void setState(State state) {
this.state = state;
}
}

View File

@ -0,0 +1,13 @@
package ru.windcorp.progressia.common.comms;
import java.io.IOException;
import ru.windcorp.progressia.common.comms.packets.Packet;
public interface CommsListener {
void onPacketReceived(Packet packet);
void onIOError(IOException reason);
}

View File

@ -0,0 +1,11 @@
package ru.windcorp.progressia.common.comms.controls;
import ru.windcorp.progressia.common.util.Namespaced;
public class ControlData extends Namespaced {
public ControlData(String namespace, String name) {
super(namespace, name);
}
}

View File

@ -0,0 +1,13 @@
package ru.windcorp.progressia.common.comms.controls;
import ru.windcorp.progressia.common.util.NamespacedRegistry;
public class ControlDataRegistry extends NamespacedRegistry<ControlData> {
private static final ControlDataRegistry INSTANCE = new ControlDataRegistry();
public static ControlDataRegistry getInstance() {
return INSTANCE;
}
}

View File

@ -0,0 +1,18 @@
package ru.windcorp.progressia.common.comms.controls;
import ru.windcorp.progressia.common.comms.packets.Packet;
public class PacketControl extends Packet {
private final ControlData control;
public PacketControl(String namespace, String name, ControlData control) {
super(namespace, name);
this.control = control;
}
public ControlData getControl() {
return control;
}
}

View File

@ -0,0 +1,11 @@
package ru.windcorp.progressia.common.comms.packets;
import ru.windcorp.progressia.common.util.Namespaced;
public class Packet extends Namespaced {
public Packet(String namespace, String name) {
super(namespace, name);
}
}

View File

@ -0,0 +1,13 @@
package ru.windcorp.progressia.common.comms.packets;
import ru.windcorp.progressia.common.world.WorldData;
public abstract class PacketWorldChange extends Packet {
public PacketWorldChange(String namespace, String name) {
super(namespace, name);
}
public abstract void apply(WorldData world);
}

View File

@ -0,0 +1,107 @@
package ru.windcorp.progressia.common.util;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.google.errorprone.annotations.DoNotCall;
public class NamespacedRegistry<E extends Namespaced>
implements Map<String, E> {
private final Map<String, E> backingMap =
Collections.synchronizedMap(new HashMap<>());
public void register(E element) {
backingMap.put(element.getId(), element);
}
public void registerAll(Collection<? extends E> elements) {
for (E element : elements) {
register(element);
}
}
@Override
public int size() {
return backingMap.size();
}
@Override
public boolean isEmpty() {
return backingMap.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return backingMap.containsKey(key);
}
public boolean has(String id) {
return backingMap.containsKey(id);
}
@Override
public boolean containsValue(Object value) {
return backingMap.containsValue(value);
}
public boolean isRegistered(E element) {
return has(element.getId());
}
@Override
public E get(Object key) {
return backingMap.get(key);
}
/**
* Use {@link #register(E)}.
*/
@Override
@DoNotCall @Deprecated
public E put(String key, E value) {
throw new UnsupportedOperationException(
"Use NamespacedRegistry.register(E)"
);
}
@Override
public E remove(Object key) {
return backingMap.remove(key);
}
/**
* Use {@link #registerAll(Collection)}.
*/
@Override
@DoNotCall @Deprecated
public void putAll(Map<? extends String, ? extends E> m) {
throw new UnsupportedOperationException(
"Use NamespacedRegistry.registerAll(Collection<? extends E>)"
);
}
@Override
public void clear() {
backingMap.clear();
}
@Override
public Set<String> keySet() {
return backingMap.keySet();
}
@Override
public Collection<E> values() {
return backingMap.values();
}
@Override
public Set<Entry<String, E>> entrySet() {
return backingMap.entrySet();
}
}

View File

@ -0,0 +1,111 @@
/*******************************************************************************
* Progressia
* Copyright (C) 2020 Wind Corporation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*******************************************************************************/
package ru.windcorp.progressia.common.util;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BooleanSupplier;
import ru.windcorp.jputil.functions.ThrowingRunnable;
public class TaskQueue {
private final Queue<Runnable> queue = new ConcurrentLinkedQueue<>();
private final BooleanSupplier runNow;
public TaskQueue(BooleanSupplier runNow) {
this.runNow = runNow;
}
public void invokeLater(Runnable task) {
queue.add(task);
}
public void invokeNow(Runnable task) {
if (runNow.getAsBoolean()) {
task.run();
} else {
invokeLater(task);
}
}
private final Object waitAndInvokeMonitor = new Object();
@SuppressWarnings("unchecked")
public <E extends Exception> void waitAndInvoke(
ThrowingRunnable<E> task
) throws InterruptedException, E {
if (runNow.getAsBoolean()) {
task.run();
return;
}
final AtomicBoolean flag =
new AtomicBoolean(false);
final AtomicReference<Throwable> thrownContainer =
new AtomicReference<>(null);
invokeLater(() -> {
try {
task.run();
} catch (Throwable t) {
thrownContainer.set(t);
}
flag.set(true);
synchronized (waitAndInvokeMonitor) {
waitAndInvokeMonitor.notifyAll();
}
});
while (!flag.get()) {
synchronized (waitAndInvokeMonitor) {
waitAndInvokeMonitor.wait();
}
}
Throwable thrown = thrownContainer.get();
if (thrown != null) {
if (thrown instanceof RuntimeException) {
throw (RuntimeException) thrown;
}
if (thrown instanceof Error) {
throw (Error) thrown;
}
throw (E) thrown; // Guaranteed
}
}
public void runTasks() {
Iterator<Runnable> tasks = queue.iterator();
while (tasks.hasNext()) {
tasks.next().run();
tasks.remove();
}
}
}

View File

@ -0,0 +1,30 @@
package ru.windcorp.progressia.common.util;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
public class VectorUtil {
public static void forEachVectorInCuboid(
int x0, int y0, int z0,
int x1, int y1, int z1,
Consumer<Vec3i> action
) {
Vec3i cursor = Vectors.grab3i();
for (int x = x0; x < x1; ++x) {
for (int y = y0; y < y1; ++y) {
for (int z = z0; z < z1; ++z) {
cursor.set(x, y, z);
action.accept(cursor);
}
}
}
Vectors.release(cursor);
}
private VectorUtil() {}
}

View File

@ -21,6 +21,7 @@ import static ru.windcorp.progressia.common.block.BlockFace.*;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import com.google.common.collect.Lists;
@ -31,11 +32,12 @@ import ru.windcorp.progressia.common.block.BlockFace;
import ru.windcorp.progressia.common.block.TileData;
import ru.windcorp.progressia.common.block.TileDataRegistry;
import ru.windcorp.progressia.common.util.SizeLimitedList;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
public class ChunkData {
public static final int BLOCKS_PER_CHUNK = 16;
public static final int BLOCKS_PER_CHUNK = Coordinates.CHUNK_SIZE;
public static final int TILES_PER_FACE = 8;
private final Vec3i position = new Vec3i();
@ -240,6 +242,14 @@ public class ChunkData {
(blockInChunk.z == max && face == TOP );
}
public void forEachBlock(Consumer<Vec3i> action) {
VectorUtil.forEachVectorInCuboid(
0, 0, 0,
BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK, BLOCKS_PER_CHUNK,
action
);
}
public int getX() {
return position.x;
}

View File

@ -0,0 +1,141 @@
package ru.windcorp.progressia.common.world;
import static ru.windcorp.progressia.common.world.ChunkData.BLOCKS_PER_CHUNK;
import glm.vec._3.i.Vec3i;
/**
* Contains static methods to convert world block coordinates, chunk block
* coordinates and chunk coordinates.
* <p>
* Three types of coordinates are used in Progressia:
* <ul>
*
* <li><em>World coordinates</em>, in code referred to as {@code blockInWorld} -
* coordinates relative to world origin. Every block in the world has unique
* world coordinates.</li>
*
* <li><em>Chunk coordinates</em>, in code referred to as {@code blockInChunk} -
* coordinates relative some chunk's origin. Every block in the chunk has unique
* chunk coordinates, but blocks in different chunks may have identical chunk
* coordinates. These coordinates are only useful in combination with a chunk
* reference. Chunk coordinates are always <tt>[0; {@link #BLOCKS_PER_CHUNK})
* </tt>.</li>
*
* <li><em>Coordinates of chunk</em>, in code referred to as {@code chunk} -
* chunk coordinates relative to world origin. Every chunk in the world has
* unique coordinates of chunk.</li>
*
* </ul>
*/
public class Coordinates {
public static final int BITS_IN_CHUNK_COORDS = 4;
public static final int CHUNK_SIZE = 1 << BITS_IN_CHUNK_COORDS;
public static final int CHUNK_COORDS_MASK = CHUNK_SIZE - 1;
/**
* Computes the coordinate of the chunk that the block specified by the
* provided world coordinate belongs to.
*
* @param blockInWorld world coordinate of the block
* @return the corresponding coordinate of the chunk containing the block
*
* @see #convertInWorldToChunk(Vec3i, Vec3i)
*/
public static int convertInWorldToChunk(int blockInWorld) {
return blockInWorld >> BITS_IN_CHUNK_COORDS;
}
/**
* Computes the coordinates of the chunk that the block specified by the
* provided world coordinates belongs to.
*
* @param blockInWorld world coordinates of the block
* @param output a {@link Vec3i} to store the result in
* @return {@code output}
*
* @see #convertInWorldToChunk(int)
*/
public static Vec3i convertInWorldToChunk(
Vec3i blockInWorld,
Vec3i output
) {
output.x = convertInWorldToChunk(blockInWorld.x);
output.y = convertInWorldToChunk(blockInWorld.y);
output.z = convertInWorldToChunk(blockInWorld.z);
return output;
}
/**
* Computes the chunk coordinate that the block specified by the
* provided world coordinate has in its chunk.
*
* @param blockInWorld world coordinate of the block
* @return the corresponding chunk coordinate of the block
*
* @see #convertInWorldToInChunk(Vec3i, Vec3i)
*/
public static int convertInWorldToInChunk(int blockInWorld) {
return blockInWorld & CHUNK_COORDS_MASK;
}
/**
* Computes the chunk coordinates that the block specified by the
* provided world coordinates has in its chunk.
*
* @param blockInWorld world coordinates of the block
* @param output a {@link Vec3i} to store the result in
* @return {@code output}
*
* @see #convertInWorldToInChunk(int)
*/
public static Vec3i convertInWorldToInChunk(
Vec3i blockInWorld,
Vec3i output
) {
output.x = convertInWorldToInChunk(blockInWorld.x);
output.y = convertInWorldToInChunk(blockInWorld.y);
output.z = convertInWorldToInChunk(blockInWorld.z);
return output;
}
/**
* Computes the world coordinate of the block specified by its chunk
* coordinate and the coordinate of its chunk.
*
* @param chunk coordinate of chunk
* @param blockInChunk chunk coordinate of block
* @return corresponding world coordinate
*
* @see #getInWorld(int, int)
*/
public static int getInWorld(int chunk, int blockInChunk) {
return blockInChunk | (chunk << BITS_IN_CHUNK_COORDS);
}
/**
* Computes the world coordinates of the block specified by its chunk
* coordinates and the coordinates of its chunk.
*
* @param chunk coordinate of chunk
* @param blockInChunk chunk coordinate of block
* @param output a {@link Vec3i} to store the result in
* @return {@code output}
*
* @see #getInWorld(int)
*/
public static Vec3i getInWorld(
Vec3i chunk, Vec3i blockInChunk,
Vec3i output
) {
output.x = getInWorld(chunk.x, blockInChunk.x);
output.y = getInWorld(chunk.y, blockInChunk.y);
output.z = getInWorld(chunk.z, blockInChunk.z);
return output;
}
}

View File

@ -24,6 +24,7 @@ import glm.vec._3.i.Vec3i;
import gnu.trove.map.TLongObjectMap;
import gnu.trove.map.hash.TLongObjectHashMap;
import ru.windcorp.progressia.common.util.CoordinatePacker;
import ru.windcorp.progressia.common.util.Vectors;
public class WorldData {
@ -44,6 +45,14 @@ public class WorldData {
return chunks.get(key);
}
public ChunkData getChunkByBlock(Vec3i blockInWorld) {
Vec3i chunkPos = Vectors.grab3i();
Coordinates.convertInWorldToChunk(blockInWorld, chunkPos);
ChunkData result = getChunk(chunkPos);
Vectors.release(chunkPos);
return result;
}
public Collection<ChunkData> getChunks() {
return Collections.unmodifiableCollection(chunks.valueCollection());
}

View File

@ -0,0 +1,78 @@
package ru.windcorp.progressia.server;
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;
public class Server {
public static Server getCurrentServer() {
return ServerThread.getCurrentServer();
}
private final WorldLogic world;
private final ImplementedChangeTracker adHocChanger =
new ImplementedChangeTracker();
private final ServerThread serverThread;
private final ClientManager clientManager = new ClientManager(this);
private final TaskQueue taskQueue = new TaskQueue(this::isServerThread);
public Server(WorldData world) {
this.world = new WorldLogic(world);
this.serverThread = new ServerThread(this);
}
public WorldLogic getWorld() {
return world;
}
/**
* Do not use in ticks
*/
public Changer getAdHocChanger() {
return adHocChanger;
}
public ClientManager getClientManager() {
return clientManager;
}
public boolean isServerThread() {
return getCurrentServer() == this;
}
public void invokeLater(Runnable task) {
taskQueue.invokeLater(task);
}
public void invokeNow(Runnable task) {
taskQueue.invokeNow(task);
}
public <E extends Exception> void waitAndInvoke(
ThrowingRunnable<E> task
) throws InterruptedException, E {
taskQueue.waitAndInvoke(task);
}
public void start() {
this.serverThread.start();
}
public void tick() {
taskQueue.runTasks();
adHocChanger.applyChanges(this);
}
public void shutdown(String message) {
// Do nothing
}
}

View File

@ -0,0 +1,25 @@
package ru.windcorp.progressia.server;
import ru.windcorp.progressia.common.world.WorldData;
public class ServerState {
private static Server instance = null;
public static Server getInstance() {
return instance;
}
public static void setInstance(Server instance) {
ServerState.instance = instance;
}
public static void startServer() {
Server server = new Server(new WorldData());
setInstance(server);
server.start();
}
private ServerState() {}
}

View File

@ -0,0 +1,61 @@
package ru.windcorp.progressia.server;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import ru.windcorp.progressia.server.world.Ticker;
public class ServerThread implements Runnable {
private static final ThreadLocal<Server> SERVER_THREADS_MAP =
new ThreadLocal<>();
public static Server getCurrentServer() {
return SERVER_THREADS_MAP.get();
}
private class ServerThreadTracker implements Runnable {
private final Runnable payload;
public ServerThreadTracker(Runnable payload) {
this.payload = payload;
}
@Override
public void run() {
SERVER_THREADS_MAP.set(getServer());
payload.run();
}
}
private final Server server;
private final ScheduledExecutorService executor =
Executors.newSingleThreadScheduledExecutor(
r -> new Thread(new ServerThreadTracker(r), "Server thread")
);
private final Ticker ticker;
public ServerThread(Server server) {
this.server = server;
this.ticker = new Ticker(server);
}
public void start() {
executor.scheduleAtFixedRate(this, 0, 1000 / 20, TimeUnit.MILLISECONDS);
}
@Override
public void run() {
server.tick();
ticker.run();
}
public Server getServer() {
return server;
}
}

View File

@ -0,0 +1,11 @@
package ru.windcorp.progressia.server.block;
import ru.windcorp.progressia.common.util.Namespaced;
public class BlockLogic extends Namespaced {
public BlockLogic(String namespace, String name) {
super(namespace, name);
}
}

View File

@ -0,0 +1,18 @@
package ru.windcorp.progressia.server.block;
import java.util.HashMap;
import java.util.Map;
public class BlockLogicRegistry {
private static final Map<String, BlockLogic> REGISTRY = new HashMap<>();
public static BlockLogic get(String name) {
return REGISTRY.get(name);
}
public static void register(BlockLogic blockLogic) {
REGISTRY.put(blockLogic.getId(), blockLogic);
}
}

View File

@ -0,0 +1,29 @@
package ru.windcorp.progressia.server.block;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.block.BlockData;
import ru.windcorp.progressia.server.world.ChunkTickContext;
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();
default BlockLogic getBlock() {
return getChunk().getBlock(getChunkCoords());
}
default BlockData getBlockData() {
return getChunkData().getBlock(getChunkCoords());
}
}

View File

@ -0,0 +1,71 @@
package ru.windcorp.progressia.server.block;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.block.BlockData;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.world.ChunkLogic;
import ru.windcorp.progressia.server.world.WorldLogic;
public class ForwardingBlockTickContext {
private BlockTickContext parent;
public ForwardingBlockTickContext(BlockTickContext parent) {
setParent(parent);
}
public BlockTickContext getParent() {
return parent;
}
public void setParent(BlockTickContext parent) {
this.parent = parent;
}
public ChunkLogic getChunk() {
return parent.getChunk();
}
public ChunkData getChunkData() {
return parent.getChunkData();
}
public double getTickLength() {
return parent.getTickLength();
}
public Server getServer() {
return parent.getServer();
}
public Vec3i getCoords() {
return parent.getCoords();
}
public WorldLogic getWorld() {
return parent.getWorld();
}
public WorldData getWorldData() {
return parent.getWorldData();
}
public Vec3i getChunkCoords() {
return parent.getChunkCoords();
}
public void requestBlockTick(Vec3i blockInWorld) {
parent.requestBlockTick(blockInWorld);
}
public BlockLogic getBlock() {
return parent.getBlock();
}
public BlockData getBlockData() {
return parent.getBlockData();
}
}

View File

@ -0,0 +1,17 @@
package ru.windcorp.progressia.server.block;
import ru.windcorp.progressia.server.world.Changer;
public interface Tickable {
void tick(BlockTickContext context, Changer changer);
default boolean doesTickRegularly(BlockTickContext context) {
return false;
}
default boolean doesTickRandomly(BlockTickContext context) {
return false;
}
}

View File

@ -0,0 +1,9 @@
package ru.windcorp.progressia.server.block;
import ru.windcorp.progressia.server.world.Changer;
public interface Updatable {
void update(BlockTickContext context, Changer changer);
}

View File

@ -0,0 +1,23 @@
package ru.windcorp.progressia.server.comms;
import ru.windcorp.progressia.common.comms.CommsChannel;
public abstract class Client extends CommsChannel {
private final int id;
public Client(int id, Role... roles) {
super(roles);
this.id = id;
}
public int getId() {
return id;
}
@Override
public String toString() {
return getClass().getSimpleName() + " " + id;
}
}

View File

@ -0,0 +1,66 @@
package ru.windcorp.progressia.server.comms;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger;
import gnu.trove.TCollections;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import ru.windcorp.progressia.common.comms.CommsChannel.Role;
import ru.windcorp.progressia.common.comms.CommsChannel.State;
import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.server.Server;
public class ClientManager {
private final Server server;
private final TIntObjectMap<Client> clientsById =
TCollections.synchronizedMap(new TIntObjectHashMap<>());
private final Collection<Client> clients =
Collections.unmodifiableCollection(clientsById.valueCollection());
private final AtomicInteger nextId = new AtomicInteger(0);
public ClientManager(Server server) {
this.server = server;
}
public int grabClientId() {
return nextId.getAndIncrement();
}
public void addClient(Client client) {
clientsById.put(client.getId(), client);
client.addListener(new DefaultServerCommsListener(this, client));
}
public void disconnectClient(Client client) {
client.disconnect();
clientsById.remove(client.getId());
}
public void broadcastGamePacket(Packet packet) {
getClients().forEach(c -> {
if (c.getState() != State.CONNECTED) return;
if (!c.getRoles().contains(Role.GAME)) return;
c.sendPacket(packet);
});
}
public Collection<Client> getClients() {
return clients;
}
public Client getById(int id) {
return clientsById.get(id);
}
public Server getServer() {
return server;
}
}

View File

@ -0,0 +1,40 @@
package ru.windcorp.progressia.server.comms;
import java.io.IOException;
import ru.windcorp.progressia.common.comms.CommsChannel.Role;
import ru.windcorp.progressia.common.comms.controls.PacketControl;
import ru.windcorp.progressia.common.comms.CommsListener;
import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.server.comms.controls.ControlLogicRegistry;
public class DefaultServerCommsListener implements CommsListener {
private final ClientManager manager;
private final Client client;
public DefaultServerCommsListener(ClientManager manager, Client client) {
this.manager = manager;
this.client = client;
}
@Override
public void onPacketReceived(Packet packet) {
if (client.getRoles().contains(Role.GAME)) {
if (packet instanceof PacketControl) {
PacketControl packetControl = (PacketControl) packet;
ControlLogicRegistry.getInstance().get(
packetControl.getControl().getId()
).apply(manager.getServer(), packetControl, client);
}
}
}
@Override
public void onIOError(IOException reason) {
// TODO Auto-generated method stub
}
}

View File

@ -0,0 +1,20 @@
package ru.windcorp.progressia.server.comms.controls;
import ru.windcorp.progressia.common.comms.controls.PacketControl;
import ru.windcorp.progressia.common.util.Namespaced;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.comms.Client;
public abstract class ControlLogic extends Namespaced {
public ControlLogic(String namespace, String name) {
super(namespace, name);
}
public abstract void apply(
Server server,
PacketControl packet,
Client client
);
}

View File

@ -0,0 +1,14 @@
package ru.windcorp.progressia.server.comms.controls;
import ru.windcorp.progressia.common.util.NamespacedRegistry;
public class ControlLogicRegistry extends NamespacedRegistry<ControlLogic> {
private static final ControlLogicRegistry INSTANCE =
new ControlLogicRegistry();
public static ControlLogicRegistry getInstance() {
return INSTANCE;
}
}

View File

@ -0,0 +1,16 @@
package ru.windcorp.progressia.server.world;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.block.BlockData;
import ru.windcorp.progressia.common.block.BlockFace;
import ru.windcorp.progressia.common.block.TileData;
public interface Changer {
void setBlock(Vec3i pos, BlockData block);
void addTile(Vec3i block, BlockFace face, TileData tile);
void removeTile(Vec3i block, BlockFace face, TileData tile);
}

View File

@ -0,0 +1,69 @@
package ru.windcorp.progressia.server.world;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.BiConsumer;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.server.block.BlockLogic;
import ru.windcorp.progressia.server.block.BlockLogicRegistry;
import ru.windcorp.progressia.server.block.Tickable;
public class ChunkLogic {
private final WorldLogic world;
private final ChunkData data;
private final Collection<Vec3i> ticking = new ArrayList<>();
public ChunkLogic(WorldLogic world, ChunkData data) {
this.world = world;
this.data = data;
generateTickList();
}
private void generateTickList() {
MutableBlockTickContext blockTickContext =
new MutableBlockTickContext();
blockTickContext.setWorld(getWorld());
blockTickContext.setChunk(this);
data.forEachBlock(blockInChunk -> {
BlockLogic block = getBlock(blockInChunk);
if (block instanceof Tickable) {
blockTickContext.setCoordsInChunk(blockInChunk);
if (((Tickable) block).doesTickRegularly(blockTickContext)) {
ticking.add(new Vec3i(blockInChunk));
}
}
});
}
public WorldLogic getWorld() {
return world;
}
public ChunkData getData() {
return data;
}
public boolean hasTickingBlocks() {
return ticking.isEmpty();
}
public void forEachTickingBlock(BiConsumer<Vec3i, BlockLogic> action) {
ticking.forEach(blockInChunk -> {
action.accept(blockInChunk, getBlock(blockInChunk));
});
}
public BlockLogic getBlock(Vec3i blockInChunk) {
return BlockLogicRegistry.get(getData().getBlock(blockInChunk).getId());
}
}

View File

@ -0,0 +1,13 @@
package ru.windcorp.progressia.server.world;
import ru.windcorp.progressia.common.world.ChunkData;
public interface ChunkTickContext extends TickContext {
ChunkLogic getChunk();
default ChunkData getChunkData() {
return getChunk().getData();
}
}

View File

@ -0,0 +1,155 @@
package ru.windcorp.progressia.server.world;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.block.BlockData;
import ru.windcorp.progressia.common.block.BlockFace;
import ru.windcorp.progressia.common.block.TileData;
import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.common.comms.packets.PacketWorldChange;
import ru.windcorp.progressia.common.util.LowOverheadCache;
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.server.Server;
public class ImplementedChangeTracker implements Changer {
public static interface Change {
void applyOnServer(WorldData world);
Packet asPacket();
}
private static class SetBlock
extends PacketWorldChange
implements Change {
private final Vec3i position = new Vec3i();
private BlockData block;
public SetBlock() {
super("Core", "SetBlock");
}
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);
Vectors.release(blockInChunk);
}
@Override
public void apply(WorldData world) {
applyOnServer(world);
}
@Override
public Packet asPacket() {
return this;
}
}
private static class AddOrRemoveTile
extends PacketWorldChange
implements Change {
private final Vec3i position = new Vec3i();
private BlockFace face;
private TileData tile;
private boolean shouldAdd;
public AddOrRemoveTile() {
super("Core", "AddOrRemoveTile");
}
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 final List<Change> changes = new ArrayList<>(1024);
private final LowOverheadCache<SetBlock> setBlockCache =
new LowOverheadCache<>(SetBlock::new);
private final LowOverheadCache<AddOrRemoveTile> addOrRemoveTileCache =
new LowOverheadCache<>(AddOrRemoveTile::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);
}
public void applyChanges(Server server) {
changes.forEach(c -> c.applyOnServer(server.getWorld().getData()));
changes.stream().map(Change::asPacket).filter(Objects::nonNull).forEach(
server.getClientManager()::broadcastGamePacket
);
changes.clear();
}
}

View File

@ -0,0 +1,91 @@
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.block.BlockTickContext;
public class MutableBlockTickContext implements BlockTickContext {
private double tickLength;
private Server server;
private WorldLogic world;
private ChunkLogic chunk;
private final Vec3i blockInWorld = new Vec3i();
private final Vec3i blockInChunk = new Vec3i();
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 Vec3i getCoords() {
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());
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
public void requestBlockTick(Vec3i blockInWorld) {
// TODO implement
throw new UnsupportedOperationException("Not yet implemented");
}
}

View File

@ -0,0 +1,23 @@
package ru.windcorp.progressia.server.world;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.server.Server;
public interface TickContext {
double getTickLength();
Server getServer();
default WorldLogic getWorld() {
return getServer().getWorld();
}
default WorldData getWorldData() {
return getWorld().getData();
}
void requestBlockTick(Vec3i blockInWorld);
}

View File

@ -0,0 +1,61 @@
package ru.windcorp.progressia.server.world;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.block.Tickable;
public class Ticker implements Runnable {
private final ImplementedChangeTracker tracker =
new ImplementedChangeTracker();
private final MutableBlockTickContext blockTickContext =
new MutableBlockTickContext();
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 context = this.blockTickContext;
context.setServer(server);
context.setChunk(chunk);
tickRegularBlocks(chunk, context);
tickRandomBlocks(chunk, context);
flushChanges(chunk);
}
private void tickRegularBlocks(
ChunkLogic chunk,
MutableBlockTickContext context
) {
chunk.forEachTickingBlock((blockInChunk, block) -> {
context.setCoordsInChunk(blockInChunk);
((Tickable) block).tick(context, tracker);
});
}
private void tickRandomBlocks(
ChunkLogic chunk,
MutableBlockTickContext context
) {
// TODO implement
}
private void flushChanges(ChunkLogic chunk) {
this.tracker.applyChanges(server);
}
}

View File

@ -0,0 +1,41 @@
package ru.windcorp.progressia.server.world;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.WorldData;
public class WorldLogic {
private final WorldData data;
private final Map<ChunkData, ChunkLogic> chunks = new HashMap<>();
public WorldLogic(WorldData data) {
this.data = data;
for (ChunkData chunkData : data.getChunks()) {
chunks.put(chunkData, new ChunkLogic(this, chunkData));
}
}
public WorldData getData() {
return data;
}
public ChunkLogic getChunk(ChunkData chunkData) {
return chunks.get(chunkData);
}
public ChunkLogic getChunk(Vec3i pos) {
return chunks.get(getData().getChunk(pos));
}
public Collection<ChunkLogic> getChunks() {
return chunks.values();
}
}