Browse Source

ZeusServer 0.0.1

Initial Commit to github
Multithreaded clientthread map generation with threadexecutorpool
master
aurailus 4 years ago
commit
4794af1895
  1. 12
      ZeusServer.iml
  2. 3
      src/main/java/META-INF/MANIFEST.MF
  3. 51
      src/main/java/ZeusServer/Helpers/Bytes.java
  4. 73
      src/main/java/ZeusServer/Helpers/ChunkSerializer.java
  5. 2140
      src/main/java/ZeusServer/Helpers/OpenSimplexNoise.java
  6. 9
      src/main/java/ZeusServer/Helpers/PacketData.java
  7. 7
      src/main/java/ZeusServer/Helpers/PacketType.java
  8. 95
      src/main/java/ZeusServer/Helpers/RLE.java
  9. 29
      src/main/java/ZeusServer/Helpers/VecUtils.java
  10. 150
      src/main/java/ZeusServer/Networking/ClientThread.java
  11. 148
      src/main/java/ZeusServer/Networking/Pacman.java
  12. 24
      src/main/java/ZeusServer/Server/GameServer.java
  13. 13
      src/main/java/ZeusServer/Server/Main.java
  14. 90
      src/main/java/ZeusServer/Server/MapGen.java

12
ZeusServer.iml

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Helpers" level="project" />
</component>
</module>

3
src/main/java/META-INF/MANIFEST.MF

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: ZeusServer.Server.Main

51
src/main/java/ZeusServer/Helpers/Bytes.java

@ -0,0 +1,51 @@
package ZeusServer.Helpers;
import java.nio.ByteBuffer;
public class Bytes {
// Long
public static byte[] longToBytes(long x) {
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.putLong(x);
return buffer.array();
}
public static long bytesToLong(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.put(bytes);
buffer.flip();
return buffer.getLong();
}
// Integer
public static byte[] intToBytes(int x) {
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
buffer.putInt(x);
return buffer.array();
}
public static int bytesToInt(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
buffer.put(bytes);
buffer.flip();
return buffer.getInt();
}
// Short
public static byte[] shortToBytes(short x) {
ByteBuffer buffer = ByteBuffer.allocate(Short.BYTES);
buffer.putShort(x);
return buffer.array();
}
public static short bytesToShort(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.allocate(Short.BYTES);
buffer.put(bytes);
buffer.flip();
return buffer.getShort();
}
}

73
src/main/java/ZeusServer/Helpers/ChunkSerializer.java

@ -0,0 +1,73 @@
package ZeusServer.Helpers;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import static ZeusServer.Helpers.Bytes.*;
public class ChunkSerializer {
public static byte[] encodeChunk(short[] chunk, ArrayList<short[]> sides) {
ArrayList<Byte> list = new ArrayList<>();
byte[] rleArray = RLE.encode(chunk);
byte[] size = intToBytes(rleArray.length);
list.add(size[0]);
list.add(size[1]);
list.add(size[2]);
list.add(size[3]);
ByteArrayInputStream rleStream = new ByteArrayInputStream(rleArray);
while (rleStream.available() > 0) list.add((byte)rleStream.read());
for (short[] shorts : sides) {
addAdjacent(list, shorts);
}
byte[] array = new byte[list.size()];
for (var i = 0; i < list.size(); i++) {
array[i] = list.get(i);
}
ByteArrayOutputStream bos;
try {
bos = new ByteArrayOutputStream(array.length);
GZIPOutputStream gzip = new GZIPOutputStream(bos);
gzip.write(array);
gzip.close();
}
catch (IOException e) {
e.printStackTrace();
return null;
}
byte[] compressed = bos.toByteArray();
try {
bos.close();
}
catch (IOException e) {
e.printStackTrace();
return null;
}
return compressed;
}
private static void addAdjacent(ArrayList<Byte> list, short[] adj) {
byte[] rleArray = RLE.encode(adj);
byte[] size = intToBytes(rleArray.length);
list.add(size[0]);
list.add(size[1]);
list.add(size[2]);
list.add(size[3]);
ByteArrayInputStream rleStream = new ByteArrayInputStream(rleArray);
while (rleStream.available() > 0) list.add((byte)rleStream.read());
}
}

2140
src/main/java/ZeusServer/Helpers/OpenSimplexNoise.java

File diff suppressed because it is too large Load Diff

9
src/main/java/ZeusServer/Helpers/PacketData.java

@ -0,0 +1,9 @@
package ZeusServer.Helpers;
import ZeusServer.Helpers.PacketType;
public class PacketData {
public long time;
public PacketType type;
public byte[] data;
}

7
src/main/java/ZeusServer/Helpers/PacketType.java

@ -0,0 +1,7 @@
package ZeusServer.Helpers;
public enum PacketType {
DEBUG,
REQUEST_CHUNK,
BLOCK_CHUNK,
}

95
src/main/java/ZeusServer/Helpers/RLE.java

@ -0,0 +1,95 @@
package ZeusServer.Helpers;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
public class RLE {
public static byte[] encode(boolean[] booleans) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
boolean type = booleans[0];
short count = 0;
for (boolean bool : booleans) {
if (bool != type) {
bos.write(new byte[]{(byte) (count & 0xff), (byte) ((count >> 8) & 0xff)});
bos.write(new byte[]{(byte) (type ? 1 : 0)});
type = bool;
count = 0;
}
count++;
}
bos.write(new byte[]{(byte) (count & 0xff), (byte) ((count >> 8) & 0xff)});
bos.write(new byte[]{(byte) (type ? 1 : 0)});
return bos.toByteArray();
}
catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static byte[] encode(short[] shorts) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
short type = shorts[0];
short count = 0;
for (short val: shorts) {
if (val != type) {
bos.write(new byte[]{(byte) (count & 0xff), (byte) ((count >> 8) & 0xff)});
bos.write(new byte[]{(byte) (type & 0xff), (byte) ((type >> 8) & 0xff)});
type = val;
count = 0;
}
count++;
}
bos.write(new byte[]{(byte) (count & 0xff), (byte) ((count >> 8) & 0xff)});
bos.write(new byte[]{(byte) (type & 0xff), (byte) ((type >> 8) & 0xff)});
return bos.toByteArray();
}
catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static short[] decodeShorts(byte[] bytes) {
ArrayList<Short> shorts = new ArrayList<>();
for (var i = 0; i < bytes.length; i+=4) {
short len = (short)(((bytes[i+1] & 0xFF) << 8) | (bytes[i] & 0xFF));
short val = (short)(((bytes[i+3] & 0xFF) << 8) | (bytes[i+2] & 0xFF));
for (var j = 0; j < len; j++) {
shorts.add(val);
}
}
short[] arr = new short[shorts.size()];
for (var i = 0; i < shorts.size(); i++) {
arr[i] = shorts.get(i);
}
return arr;
}
public static boolean[] decodeBools(byte[] bytes) {
ArrayList<Boolean> booleans = new ArrayList<>();
for (var i = 0; i < bytes.length; i+=3) {
short len = (short)(((bytes[i+1] & 0xFF) << 8) | (bytes[i] & 0xFF));
boolean val = (bytes[i+2] & 0xFF)==1;
for (var j = 0; j < len; j++) {
booleans.add(val);
}
}
boolean[] arr = new boolean[booleans.size()];
for (var i = 0; i < booleans.size(); i++) {
arr[i] = booleans.get(i);
}
return arr;
}
}

29
src/main/java/ZeusServer/Helpers/VecUtils.java

@ -0,0 +1,29 @@
package ZeusServer.Helpers;
import org.joml.Vector3i;
public class VecUtils {
public static Vector3i stringToVector(String in) {
String[] coords = in.split(",");
if (coords.length != 3) return null;
Vector3i pos = new Vector3i();
try {
pos.x = Integer.valueOf(coords[0]);
pos.y = Integer.valueOf(coords[1]);
pos.z = Integer.valueOf(coords[2]);
}
catch (NumberFormatException e) {
e.printStackTrace();
return null;
}
return pos;
}
public static String vectorToString(Vector3i in) {
return vectorToString(in.x, in.y, in.z);
}
public static String vectorToString(int x, int y, int z) {
return x + "," + y + "," + z;
}
}

150
src/main/java/ZeusServer/Networking/ClientThread.java

@ -0,0 +1,150 @@
package ZeusServer.Networking;
import ZeusServer.Helpers.*;
import ZeusServer.Server.MapGen;
import org.joml.Vector3i;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.sql.SQLOutput;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static ZeusServer.Server.MapGen.CHUNK_SIZE;
public class ClientThread extends Thread implements Runnable {
private Socket socket;
private Pacman pacman;
private boolean alive;
private MapGen mapGen;
private ThreadPoolExecutor mapGenPool;
private ArrayList<Future> mapGenFutures;
public ClientThread(Socket socket) {
this.socket = socket;
}
private void init() {
this.pacman = new Pacman(socket);
mapGen = new MapGen();
pacman.start();
alive = true;
mapGenPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(32);
mapGenPool.setMaximumPoolSize(64);
mapGenPool.setKeepAliveTime(32, TimeUnit.SECONDS);
mapGenFutures = new ArrayList<>();
}
private void update() {
pacman.getPackets((PacketData in) -> {
switch (in.type) {
case DEBUG:
System.out.println(new String(in.data, StandardCharsets.ISO_8859_1));
break;
case REQUEST_CHUNK:
deferredRenderChunk(in);
break;
default:
System.out.println("Recieved packet of type " + in.type + "and we can't to deal with it!");
}
});
// pacman.sendPacket(PacketType.DEBUG, "Server to client: Hi! Time is " + System.currentTimeMillis());
}
private void deferredRenderChunk(PacketData in) {
mapGenFutures.add(mapGenPool.submit(() -> {
Vector3i position = VecUtils.stringToVector(new String(in.data, StandardCharsets.ISO_8859_1));
if (position == null) return;
StringBuilder s = new StringBuilder();
s.append(VecUtils.vectorToString(position));
s.append("|");
System.out.println("Generating chunk at position " + position);
var bytes = ChunkSerializer.encodeChunk(generateChunk(position), generateSides(position));
if (bytes == null) return;
s.append(new String(bytes, StandardCharsets.ISO_8859_1));
pacman.sendPacket(PacketType.BLOCK_CHUNK, s.toString());
}));
}
private ArrayList<short[]> generateSides(Vector3i pos) {
ArrayList<short[]> sides = new ArrayList<>();
var array = new short[256];
for (var i = 0; i < 256; i++) {
array[i] = mapGen.getBlock(pos.x*CHUNK_SIZE + 16, pos.y*CHUNK_SIZE + i/16, pos.z*CHUNK_SIZE + i%16);
}
sides.add(array);
array = new short[256];
for (var i = 0; i < 256; i++) {
array[i] = mapGen.getBlock(pos.x*CHUNK_SIZE - 1, pos.y*CHUNK_SIZE + i/16, pos.z*CHUNK_SIZE + i%16);
}
sides.add(array);
array = new short[256];
for (var i = 0; i < 256; i++) {
array[i] = mapGen.getBlock(pos.x*CHUNK_SIZE + i/16, pos.y*CHUNK_SIZE + 16, pos.z*CHUNK_SIZE + i%16);
}
sides.add(array);
array = new short[256];
for (var i = 0; i < 256; i++) {
array[i] = mapGen.getBlock(pos.x*CHUNK_SIZE + i/16, pos.y*CHUNK_SIZE - 1, pos.z*CHUNK_SIZE + i%16);
}
sides.add(array);
array = new short[256];
for (var i = 0; i < 256; i++) {
array[i] = mapGen.getBlock(pos.x*CHUNK_SIZE + i%16, pos.y*CHUNK_SIZE + i/16, pos.z*CHUNK_SIZE + 16);
}
sides.add(array);
array = new short[256];
for (var i = 0; i < 256; i++) {
array[i] = mapGen.getBlock(pos.x*CHUNK_SIZE + i%16, pos.y*CHUNK_SIZE + i/16, pos.z*CHUNK_SIZE - 1);
}
sides.add(array);
return sides;
}
private short[] generateChunk(Vector3i pos) {
var chunk = new short[4096];
for (var i = 0; i < CHUNK_SIZE; i++) {
for (var j = 0; j < CHUNK_SIZE; j++) {
for (var k = 0; k < CHUNK_SIZE; k++) {
short fill = mapGen.getBlock(i + pos.x*CHUNK_SIZE, j + pos.y*CHUNK_SIZE, k + pos.z*CHUNK_SIZE);
chunk[i + CHUNK_SIZE * (j + CHUNK_SIZE * k)] = fill;
}
}
}
return chunk;
}
@Override
public void run() {
init();
while (alive) {
update();
try {
Thread.sleep(16);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

148
src/main/java/ZeusServer/Networking/Pacman.java

@ -0,0 +1,148 @@
package ZeusServer.Networking;
import ZeusServer.Helpers.Bytes;
import ZeusServer.Helpers.PacketData;
import ZeusServer.Helpers.PacketType;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class Pacman extends Thread implements Runnable {
private final int MAX_OUT_SIZE = 4096;
private final int OUT_INTERVAL = 32;
private Socket socket;
private boolean closed = false;
private DataInputStream in;
private DataOutputStream out;
private ArrayList<byte[]> pendingOutPackets;
private BlockingQueue<byte[]> pendingInPackets;
private ArrayList<PacketData> clientPackets;
Pacman(Socket socket) {
this.socket = socket;
pendingOutPackets = new ArrayList<>();
pendingInPackets = new LinkedBlockingQueue<>();
clientPackets = new ArrayList<>();
}
@Override
public void run() {
try {
in = new DataInputStream(socket.getInputStream());
out = new DataOutputStream(socket.getOutputStream());
//Background Packet Resolver
Thread thread = new Thread(() -> {
try {
while (!closed) {
int length = in.readInt();
byte[] data = new byte[length];
in.readFully(data, 0, length);
pendingInPackets.put(data);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
closed = true;
}
}, "BackgroundPacketResolver");
thread.setDaemon(true);
thread.start();
loop();
}
catch (Exception e) {
e.printStackTrace();
}
}
private void loop() throws Exception {
while (!closed) {
update();
Thread.sleep(OUT_INTERVAL);
}
socket.close();
}
private void update() throws Exception {
synchronized (this) {
//noinspection StatementWithEmptyBody
while (decodePacket()) {}
}
sendPendingOutPackets();
}
synchronized void getPackets(Consumer<PacketData> consumer) {
for (PacketData packet : clientPackets) {
consumer.accept(packet);
}
clientPackets.clear();
}
private boolean decodePacket() throws InterruptedException {
byte[] packet = (closed || pendingInPackets.isEmpty()) ? null : pendingInPackets.poll(1L, TimeUnit.MILLISECONDS);
if (packet == null) return false;
PacketData p = new PacketData();
p.type = PacketType.values()[Bytes.bytesToInt(Arrays.copyOfRange(packet, 0, 4))];
p.time = Bytes.bytesToLong(Arrays.copyOfRange(packet, 4, 12));
p.data = Arrays.copyOfRange(packet, 12, packet.length);
clientPackets.add(p);
return true;
}
private void sendPendingOutPackets() throws IOException {
int size = 0;
byte[] packet;
synchronized (this) {
while (pendingOutPackets.size() > 0) {
packet = pendingOutPackets.get(0);
size += packet.length;
out.writeInt(packet.length);
out.write(packet);
pendingOutPackets.remove(0);
if (size >= MAX_OUT_SIZE) break;
}
}
out.flush();
}
void sendPacket(PacketType p, String data) {
sendPacket(p, data.getBytes(StandardCharsets.ISO_8859_1));
}
void sendPacket(PacketType p, byte[] data) {
byte[] out = new byte[data.length + 12];
System.arraycopy(Bytes.intToBytes(p.ordinal()), 0, out, 0, 4);
System.arraycopy(Bytes.longToBytes(0), 0, out, 4, 8);
System.arraycopy(data, 0, out, 12, data.length);
synchronized (this) {
pendingOutPackets.add(out);
}
}
void kill() {
closed = true;
}
}

24
src/main/java/ZeusServer/Server/GameServer.java

@ -0,0 +1,24 @@
package ZeusServer.Server;
import ZeusServer.Networking.ClientThread;
import java.net.ServerSocket;
import java.net.Socket;
class GameServer {
private int port;
GameServer(int port) {
this.port = port;
}
void start() throws Exception {
ServerSocket socket = new ServerSocket(this.port);
while (true) {
Socket clientSocket = socket.accept();
ClientThread t = new ClientThread(clientSocket);
t.start();
}
}
}

13
src/main/java/ZeusServer/Server/Main.java

@ -0,0 +1,13 @@
package ZeusServer.Server;
import ZeusServer.Server.GameServer;
public class Main {
public static void main(String... args) throws Exception {
if (args.length <= 0) throw new Exception("No Port Specified! Port should be the first argument.");
int port = Integer.parseInt(args[0]);
GameServer server = new GameServer(port);
server.start();
}
}

90
src/main/java/ZeusServer/Server/MapGen.java

@ -0,0 +1,90 @@
package ZeusServer.Server;
import ZeusServer.Helpers.OpenSimplexNoise;
import org.joml.Vector3i;
import static java.lang.Math.floorMod;
public class MapGen {
public static final int CHUNK_SIZE = 16;
OpenSimplexNoise terrainNoise;
OpenSimplexNoise treeNoise;
private long seed;
public MapGen() {
// seed = Math.round(Math.random()*1000000);
seed = 0;
terrainNoise = new OpenSimplexNoise(seed);
treeNoise = new OpenSimplexNoise(seed/2);
}
public short getBlock(int x, int y, int z) {
int xx = (int)Math.floor((float)x/8);
int zz = (int)Math.floor((float)z/8);
float v = ((float)x/8) - xx;
float u = ((float)z/8) - zz;
float point00 = getNoisePoint(xx*8, zz*8);
float point10 = getNoisePoint((xx+1)*8, zz*8);
float point01 = getNoisePoint(xx*8, (zz+1)*8);
float point11 = getNoisePoint((xx+1)*8, (zz+1)*8);
float x1 = lerp(point00, point01, u);
float x2 = lerp(point10, point11, u);
float average = lerp(x1, x2, v);
if (y + 1 - average < -2) return 3;
if (y + 1 - average < 0) return 2;
if (y - average < 0) return 1;
if (y - average < 5)
if (Math.pow(treeNoise.eval(x, z), 2) > 0.6) return 11;
if (y - average < 5 && y - average > 3) {
for (var i = -2; i < 3; i++) {
for (var j = -2; j < 3; j++) {
if (i != 0 || j != 0) {
if (Math.pow(treeNoise.eval(x + i, z + j), 2) > 0.6) return 12;
}
}
}
}
if (y - average < 7 && y - average > 5) {
for (var i = -1; i < 2; i++) {
for (var j = -1; j < 2; j++) {
if (Math.pow(treeNoise.eval(x + i, z + j), 2) > 0.6) return 12;
}
}
}
if (y - 1 - average < 0) {
if (Math.random() > 0.2) {
return (short)(4 + Math.round(Math.random() * 4));
}
}
return 0;
}
public short getBlock(Vector3i pos) {
return getBlock(pos.x, pos.y, pos.z);
}
private int multiplyPerlin(int x, int z, int horz, int vert) {
return (int)(terrainNoise.eval((float)x / horz, (float)z / horz) * vert);
}
private static float lerp(float s, float e, float t) {
return s + (e - s) * t;
}
private float getNoisePoint(int x, int y) {
double value = multiplyPerlin(x, y, 600, 100) * 0.6;
value += multiplyPerlin(x, y, 100, 50) * 0.6;
value += multiplyPerlin(x, y, 50, 25) * 0.6;
return (float)value;
}
}
Loading…
Cancel
Save