Server Side Chunk Generation

* Server World Class
* WorldGenStream class to handle multithreaded map generation
* ServerPlayer stores active chunk boundaries

* BlockChunk stores position
* Renamed client World to LocalWorld
* Disabled client side Generation
* ServerConnection stores chunk packets to be used by the GameScene,
  which gives them to the world (change later?)

* Reenabled "BlockChunk Packet Encoding" test in tests/BlockChunk.cpp
This commit is contained in:
aurailus 2019-03-07 17:43:05 -08:00
parent 3bba3adf66
commit b66ca8284b
21 changed files with 460 additions and 86 deletions

View File

@ -110,6 +110,6 @@ set(ZEUS_SRC_FILES
client/engine/FrustumPlane.cpp
client/engine/FrustumPlane.h
client/engine/FrustumAABB.cpp
client/engine/FrustumAABB.h)
client/engine/FrustumAABB.h server/world/World.cpp server/world/World.h server/world/WorldGenStream.cpp server/world/WorldGenStream.h)
add_library (zeusCore ${ZEUS_SRC_FILES})

View File

@ -30,15 +30,15 @@ GameScene::GameScene(ClientState* state) :
//The scene requires the blockAtlas for meshing and handling inputs.
world = new LocalWorld(blockAtlas);
int SIZE = 16;
int SIZEV = 8;
for (int i = -SIZE; i < SIZE; i++) {
for (int j = -SIZE; j < SIZEV; j++) {
for (int k = -SIZE; k < SIZE; k++) {
world->genNewChunk(glm::vec3(i, j, k));
}
}
}
// int SIZE = 16;
// int SIZEV = 8;
// for (int i = -SIZE; i < SIZE; i++) {
// for (int j = -SIZE; j < SIZEV; j++) {
// for (int k = -SIZE; k < SIZE; k++) {
// world->genNewChunk(glm::vec3(i, j, k));
// }
// }
// }
player = new Player();
player->create(world, state->renderer->getCamera());
@ -62,6 +62,14 @@ void GameScene::update() {
state->renderer->resized = false;
}
while (!server->chunkPackets.empty()) {
auto it = server->chunkPackets.begin();
Packet p = *it;
server->chunkPackets.erase(it);
world->loadChunkPacket(&p);
}
debugGui.update(player, world, window, blockAtlas, state->fps, (int)world->getMeshChunks()->size(), drawCalls);
world->update();
}

View File

@ -22,21 +22,21 @@ LocalWorld::LocalWorld(BlockAtlas *atlas) {
void LocalWorld::genNewChunk(glm::vec3 pos) {
if (!blockChunks.count(pos)) {
pendingGen.insert(pos);
pendingGen.push_back(pos);
}
}
void LocalWorld::loadChunkPacket(Packet *p) {
// auto b = new BlockChunk();
//
// glm::vec3 pos = glm::vec3(Packet::decodeInt(&p->data[0]), Packet::decodeInt(&p->data[4]), Packet::decodeInt(&p->data[8]));
//
// int len = Packet::decodeInt(&p->data[12]);
// std::string data(p->data.begin() + 16, p->data.begin() + 16 + len);
//
// b->deserialize(data);
//
// commitChunk(pos, b);
auto b = new BlockChunk();
glm::vec3 pos = glm::vec3(Serializer::decodeInt(&p->data[0]), Serializer::decodeInt(&p->data[4]), Serializer::decodeInt(&p->data[8]));
int len = Serializer::decodeInt(&p->data[12]);
std::string data(p->data.begin() + 16, p->data.begin() + 16 + len);
b->deserialize(data);
commitChunk(pos, b);
}
void LocalWorld::commitChunk(glm::vec3 pos, BlockChunk *c) {
@ -61,7 +61,7 @@ void LocalWorld::attemptMeshChunk(glm::vec3 pos) {
thisChunk->adjacent[4] = getAdjacentExists(glm::vec3(pos.x, pos.y, pos.z + 1), pos);
thisChunk->adjacent[5] = getAdjacentExists(glm::vec3(pos.x, pos.y, pos.z - 1), pos);
if (thisChunk->allAdjacentsExist()) pendingMesh.insert(pos);
if (thisChunk->allAdjacentsExist()) pendingMesh.push_back(pos);
}
bool LocalWorld::getAdjacentExists(glm::vec3 pos, glm::vec3 otherPos) {
@ -77,7 +77,7 @@ bool LocalWorld::getAdjacentExists(glm::vec3 pos, glm::vec3 otherPos) {
if (diff == glm::vec3(0, 0, 1)) chunk->adjacent[4] = true;
if (diff == glm::vec3(0, 0,-1)) chunk->adjacent[5] = true;
if (chunk->allAdjacentsExist()) pendingMesh.insert(pos);
if (chunk->allAdjacentsExist()) pendingMesh.push_back(pos);
return true;
}
return false;
@ -133,6 +133,13 @@ std::vector<bool>* LocalWorld::getAdjacentsCull(glm::vec3 pos) {
}
void LocalWorld::update() {
// std::sort(pendingGen.begin(), pendingGen.begin()+min(1000, (int)pendingGen.size()), [](glm::vec3 a, glm::vec3 b) {
// return glm::distance(a, glm::vec3(0, 0, 0)) < glm::distance(b, glm::vec3(0, 0, 0));
// });
// std::sort(pendingMesh.begin(), pendingMesh.end()+min(1000, (int)pendingMesh.size()), [](glm::vec3 a, glm::vec3 b) {
// return glm::distance(a, glm::vec3(0, 0, 0)) < glm::distance(b, glm::vec3(0, 0, 0));
// });
//
handleChunkGenQueue();
handleMeshGenQueue();
}

View File

@ -100,7 +100,7 @@ private:
const int GEN_QUEUE_SIZE = 8;
const int GEN_FINISHED_SIZE = GEN_THREADS * GEN_QUEUE_SIZE;
std::unordered_set<glm::vec3, vec3cmp> pendingGen;
std::vector<glm::vec3> pendingGen;
std::vector<ChunkThreadDef*> genThreads;
std::vector<ChunkThreadData*> finishedGen;
@ -108,7 +108,7 @@ private:
const int MESH_QUEUE_SIZE = 8;
const int MESH_FINISHED_SIZE = GEN_THREADS * GEN_QUEUE_SIZE;
std::unordered_set<glm::vec3, vec3cmp> pendingMesh;
std::vector<glm::vec3> pendingMesh;
std::vector<MeshThreadDef*> meshThreads;
std::vector<MeshThreadData*> finishedMesh;

View File

@ -30,15 +30,17 @@ void ServerConnection::update(Player &player, std::vector<PlayerEntity*>& player
case ENET_EVENT_TYPE_RECEIVE: {
Packet p(event.packet);
if (p.type == Packet::PLAYER_INFO) {
switch (p.type) {
case Packet::PLAYER_INFO: {
glm::vec3 playerPos = glm::vec3(
Serializer::decodeFloat(&p.data[0]),
Serializer::decodeFloat(&p.data[4]),
Serializer::decodeFloat(&p.data[8])
);
player.setPos(playerPos);
break;
}
else if (p.type == Packet::ENTITY_INFO) {
case Packet::ENTITY_INFO: {
int peer_id = Serializer::decodeInt(&p.data[0]);
glm::vec3 playerPos = glm::vec3(
@ -55,9 +57,18 @@ void ServerConnection::update(Player &player, std::vector<PlayerEntity*>& player
break;
}
}
if (!found) {
playerEntities.push_back(new PlayerEntity(playerPos, peer_id));
}
break;
}
case Packet::CHUNK_INFO: {
chunkPackets.push_back(std::move(p));
break;
}
default:
break;
}
enet_packet_destroy(event.packet);

View File

@ -25,6 +25,7 @@ public:
~ServerConnection();
std::vector<Packet> chunkPackets;
private:
bool connected = false;

View File

@ -17,6 +17,11 @@ BlockChunk::BlockChunk(std::vector<int> blocks) {
this->blocks = std::move(blocks);
}
BlockChunk::BlockChunk(std::vector<int> blocks, glm::vec3 pos) {
this->blocks = std::move(blocks);
this->pos = pos;
}
int BlockChunk::getBlock(glm::vec3* pos) {
unsigned int ind = ArrayTrans3D::vecToInd(pos);
if (ind < 0 || ind >= 4096) return -1;

View File

@ -15,6 +15,9 @@ class BlockChunk {
public:
BlockChunk();
explicit BlockChunk(std::vector<int> blocks);
BlockChunk(std::vector<int> blocks, glm::vec3 pos);
glm::vec3 pos;
bool adjacent[6] = {false, false, false, false, false, false};
bool allAdjacentsExist();

View File

@ -58,7 +58,7 @@ BlockChunk* MapGen::generate(glm::vec3 pos) {
buildElevation(j);
fillChunk(j);
return new BlockChunk(j.blocks);
return new BlockChunk(j.blocks, pos);
}
void MapGen::buildElevation(MapGenJob &j) {

View File

@ -5,6 +5,10 @@
#include "ConnectionList.h"
#include "../generic/network/PacketChannel.h"
ConnectionList::ConnectionList(World* world) {
this->world = world;
}
ServerPeer* ConnectionList::addPeer(ENetPeer *eNetPeer) {
printf("[INFO] %x:%u connected.\n", eNetPeer->address.host, eNetPeer->address.port);
@ -31,14 +35,14 @@ void ConnectionList::removePeer(ENetPeer *eNetPeer) {
}
}
ServerPlayer* ConnectionList::addPlayer(ServerPeer *peer, std::string uuid) {
ServerPlayer* ConnectionList::createPlayer(ServerPeer *peer, std::string uuid) {
printf("[INFO] Creating player %s for %x:%u.\n", uuid.c_str(), peer->peer->address.host, peer->peer->address.port);
auto player = new ServerPlayer(peer);
player->pos = glm::vec3(-8, 32, -8);
player->setPos(glm::vec3(0, 16, 0));
//Send Initialization Data
auto packet = player->getInitPacket();
packet.sendTo(peer->peer, PacketChannel::PLAYER_INFO);
players.push_back(player);
world->addPlayer(player);
}

View File

@ -9,16 +9,19 @@
#include <string>
#include "ServerPlayer.h"
#include "ServerPeer.h"
#include "world/World.h"
class ConnectionList {
public:
explicit ConnectionList(World* world);
ServerPeer* addPeer(ENetPeer* peer);
void removePeer(ENetPeer* peer);
ServerPlayer* addPlayer(ServerPeer* peer, std::string uuid);
ServerPlayer* createPlayer(ServerPeer *peer, std::string uuid);
public:
std::vector<ServerPeer*> peers;
std::vector<ServerPlayer*> players;
World* world;
};

View File

@ -5,9 +5,9 @@
#include "Server.h"
#include "../generic/blocks/BlockChunk.h"
Server::Server() = default;
Server::Server() : connections(&world) {};
Server::Server(unsigned short port) {
Server::Server(unsigned short port) : connections(&world) {
this->port = port;
}
@ -20,12 +20,14 @@ void Server::init() {
void Server::update() {
Timer loop("");
world.update();
ENetEvent event;
while (handler.update(&event) && loop.elapsedNs() < 15L*1000000L) {
switch (event.type) {
case ENET_EVENT_TYPE_CONNECT: {
auto peer = connections.addPeer(event.peer);
connections.addPlayer(peer, "Aurailus");
connections.createPlayer(peer, "Aurailus");
break;
}
case ENET_EVENT_TYPE_RECEIVE: {
@ -43,7 +45,7 @@ void Server::update() {
Serializer::decodeFloat(&p.data[4]),
Serializer::decodeFloat(&p.data[8])
);
player->pos = newPos;
player->setPos(newPos);
//Send All Clients the new positon
Packet r(Packet::ENTITY_INFO);

View File

@ -29,6 +29,7 @@ public:
private:
bool alive = true;
World world;
NetHandler handler;
ConnectionList connections;

View File

@ -5,7 +5,6 @@
#ifndef ZEUS_SERVERPEER_H
#define ZEUS_SERVERPEER_H
#include <enet/enet.h>
class ServerPlayer;

View File

@ -2,15 +2,13 @@
// Created by aurailus on 11/01/19.
//
#include <cmath>
#include "ServerPlayer.h"
ServerPlayer::ServerPlayer(ServerPeer *peer) {
this->peer = peer;
peer->player = this;
}
ServerPlayer::~ServerPlayer() {
updateBounds();
}
Packet ServerPlayer::getInitPacket() {
@ -22,3 +20,38 @@ Packet ServerPlayer::getInitPacket() {
return p;
}
glm::vec3 ServerPlayer::getPos() {
return pos;
}
void ServerPlayer::setPos(glm::vec3 pos) {
this->lastPos = this->pos;
this->pos = pos;
glm::vec3 chunkPos(std::floor(pos.x / 16), std::floor(pos.y / 16), std::floor(pos.z / 16));
glm::vec3 lastChunkPos(std::floor(lastPos.x / 16), std::floor(lastPos.y / 16), std::floor(lastPos.z / 16));
if (chunkPos != lastChunkPos) {
updateBounds();
}
}
std::pair<glm::vec3, glm::vec3> ServerPlayer::getBounds() {
return {minBounds, maxBounds};
}
void ServerPlayer::updateBounds() {
glm::vec3 chunkPos(std::floor(pos.x / 16), std::floor(pos.y / 16), std::floor(pos.z / 16));
minBounds = glm::vec3(chunkPos.x - ACTIVE_RANGE, chunkPos.y - ACTIVE_RANGE, chunkPos.z - ACTIVE_RANGE);
maxBounds = glm::vec3(chunkPos.x + ACTIVE_RANGE, chunkPos.y + ACTIVE_RANGE, chunkPos.z + ACTIVE_RANGE);
}
bool ServerPlayer::isInBounds(glm::vec3 pos) {
return (pos.x >= minBounds.x && pos.x <= maxBounds.x
&& pos.y >= minBounds.y && pos.y <= maxBounds.y
&& pos.z >= minBounds.z && pos.z <= maxBounds.z);
}
ServerPlayer::~ServerPlayer() = default;

View File

@ -12,14 +12,29 @@
class ServerPlayer {
public:
const static int ACTIVE_RANGE = 20;
explicit ServerPlayer(ServerPeer* peer);
Packet getInitPacket();
ServerPeer* peer;
glm::vec3 pos = glm::vec3(0, 0, 0);
glm::vec3 getPos();
void setPos(glm::vec3 pos);
std::pair<glm::vec3, glm::vec3> getBounds();
bool isInBounds(glm::vec3 pos);
~ServerPlayer();
ServerPeer* peer;
private:
void updateBounds();
glm::vec3 pos = {0, 0, 0};
glm::vec3 lastPos = {0, 0, 0};
glm::vec3 minBounds = {0, 0, 0};
glm::vec3 maxBounds = {0, 0, 0};
};

View File

@ -0,0 +1,75 @@
//
// Created by aurailus on 05/03/19.
//
#include "World.h"
#include "../../generic/network/PacketChannel.h"
void World::addPlayer(ServerPlayer *player) {
this->players.push_back(player);
playerChangedChunks(player);
}
void World::playerChangedChunks(ServerPlayer *player) {
auto bounds = player->getBounds();
for (auto i = (int)bounds.first.x; i < (int)bounds.second.x; i++) {
for (auto j = (int)bounds.first.y; j < (int)bounds.second.y; j++) {
for (auto k = (int)bounds.first.z; k < (int)bounds.second.z; k++) {
generate(glm::vec3(i, j, k));
}
}
}
}
void World::generate(glm::vec3 pos) {
if(!generateQueueMap.count(pos) && !chunkMap.count(pos)) {
generateQueueMap.insert(pos);
generateQueueList.push_back(pos);
}
}
void World::update() {
while (!generateQueueList.empty()) {
auto it = generateQueueList.begin();
glm::vec3 pos = *it;
bool success = genStream.tryToQueue(pos);
if (success) {
generateQueueList.erase(it);
generateQueueMap.erase(pos);
}
else break;
}
auto finished = genStream.update();
for (auto chunk : finished) {
bool didCalcSerialized = false;
std::string serialized;
for (auto player : players) {
if (player->isInBounds(chunk->pos)) {
//Serialize the chunk
if (!didCalcSerialized) {
serialized = chunk->serialize();
didCalcSerialized = true;
}
//Send the Chunk to the player
Packet r(Packet::CHUNK_INFO);
Serializer::encodeInt(r.data, (int)chunk->pos.x);
Serializer::encodeInt(r.data, (int)chunk->pos.y);
Serializer::encodeInt(r.data, (int)chunk->pos.z);
Serializer::encodeString(r.data, serialized);
r.sendTo(player->peer->peer, PacketChannel::WORLD_INFO);
}
}
}
}

45
src/server/world/World.h Normal file
View File

@ -0,0 +1,45 @@
//
// Created by aurailus on 05/03/19.
//
#ifndef ZEUS_WORLD_H
#define ZEUS_WORLD_H
#include <unordered_map>
#include <unordered_set>
#include "../../generic/blocks/BlockChunk.h"
#include "../ServerPlayer.h"
#include "WorldGenStream.h"
class World {
public:
World() = default;
void addPlayer(ServerPlayer* player);
void update();
~World() = default;
private:
void playerChangedChunks(ServerPlayer* player);
void generate(glm::vec3 pos);
struct vec3cmp {
size_t operator()(const glm::vec3& k)const {
return std::hash<float>()(k.x) ^ std::hash<float>()(k.y) ^ std::hash<float>()(k.z);
}
};
WorldGenStream genStream;
std::vector<ServerPlayer*> players;
std::unordered_map<glm::vec3, BlockChunk*, vec3cmp> chunkMap;
std::vector<std::pair<glm::vec3, BlockChunk*>> chunkList;
std::unordered_set<glm::vec3, vec3cmp> generateQueueMap;
std::vector<glm::vec3> generateQueueList;
};
#endif //ZEUS_WORLD_H

View File

@ -0,0 +1,93 @@
//
// Created by aurailus on 06/03/19.
//
#include "WorldGenStream.h"
#pragma clang diagnostic push
#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
WorldGenStream::WorldGenStream() : gen(0) {
queuedTasks.reserve((unsigned long) TOTAL_QUEUE_SIZE);
threads.reserve(THREADS);
for (int i = 0; i < THREADS; i++) {
threads.emplace_back(&gen);
}
}
bool WorldGenStream::tryToQueue(glm::vec3 pos) {
unsigned long sizeOfQueue = queuedTasks.size();
if (sizeOfQueue < TOTAL_QUEUE_SIZE && !queuedMap.count(pos)) {
queuedTasks.push_back(pos);
queuedMap.insert(pos);
}
return sizeOfQueue + 1 < TOTAL_QUEUE_SIZE;
}
std::vector<BlockChunk*> WorldGenStream::update() {
std::vector<BlockChunk*> finishedChunks;
for (auto& t : threads) {
for (auto& u : t.tasks) {
if (!u.unlocked) continue;
if (u.chunk != nullptr) {
finishedChunks.push_back(u.chunk);
u.chunk = nullptr;
}
if (!queuedTasks.empty()) {
auto it = queuedTasks.begin();
glm::vec3 pos = *it;
queuedTasks.erase(it);
queuedMap.erase(pos);
u.pos = pos;
//Lock it in to allow the thread to edit it.
u.unlocked = false;
}
}
}
return finishedChunks;
}
WorldGenStream::Thread::Thread(MapGen *gen) {
this->gen = gen;
thread = std::thread(WorldGenStream::threadFunction, this);
thread.detach();
}
void WorldGenStream::threadFunction(WorldGenStream::Thread *thread) {
while (thread->keepAlive) {
bool empty = true;
for (Unit& u : thread->tasks) {
if (!u.unlocked) {
empty = false;
u.chunk = thread->gen->generate(u.pos);
u.unlocked = true;
break;
}
}
if (empty) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
}
WorldGenStream::~WorldGenStream() {
for (auto& t : threads) {
t.keepAlive = false;
t.thread.join();
}
}
#pragma clang diagnostic pop

View File

@ -0,0 +1,69 @@
//
// Created by aurailus on 06/03/19.
//
#pragma clang diagnostic push
#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
#ifndef ZEUS_WORLDGENSTREAM_H
#define ZEUS_WORLDGENSTREAM_H
#include <vec3.hpp>
#include <thread>
#include <unordered_set>
#include "../../generic/blocks/BlockChunk.h"
#include "../../generic/gen/MapGen.h"
class WorldGenStream {
public:
static const int THREAD_QUEUE_SIZE = 32;
static const int THREADS = 8;
static const int TOTAL_QUEUE_SIZE = THREADS * THREAD_QUEUE_SIZE;
WorldGenStream();
~WorldGenStream();
//Attempt to add `pos` to the pre-thread queue.
//Will return a boolean stating if there is more space left in the queue.
bool tryToQueue(glm::vec3 pos);
//Will return a vector of BlockChunk pointers containing finished chunks.
//Frees up the threads and starts new tasks.
std::vector<BlockChunk*> update();
struct Unit {
glm::vec3 pos {0, 0, 0};
BlockChunk* chunk = nullptr;
bool unlocked = true;
};
struct Thread {
explicit Thread(MapGen* gen);
MapGen* gen;
std::thread thread;
bool keepAlive = true;
std::vector<Unit> tasks = std::vector<Unit>(THREAD_QUEUE_SIZE);
};
std::vector<Thread> threads;
private:
static void threadFunction(Thread* thread);
struct vec3cmp {
size_t operator()(const glm::vec3& k)const {
return std::hash<float>()(k.x) ^ std::hash<float>()(k.y) ^ std::hash<float>()(k.z);
}
};
MapGen gen;
std::vector<glm::vec3> queuedTasks;
std::unordered_set<glm::vec3, vec3cmp> queuedMap;
};
#endif //ZEUS_WORLDGENSTREAM_H
#pragma clang diagnostic pop

View File

@ -47,27 +47,27 @@ TEST_CASE("Blockchunks", "[networking]") {
delete b2;
}
// SECTION("BlockChunk Packet Encoding") {
// auto p = new Packet(Packet::CHUNKINFO);
// p->addString(gzip);
//
// auto byteArr = p->serialize();
//
// auto p2 = Packet::deserialize(byteArr);
//
// int len = Packet::decodeInt(&p2->data[0]);
// std::string data(p->data.begin() + 4, p->data.begin() + 4 + len);
//
// auto b2 = new BlockChunk();
// REQUIRE(b2->deserialize(data));
//
// for (int j = 0; j < 4096; j++) {
// REQUIRE(b2->getBlock(j) == b->getBlock(j));
// }
//
// delete b2;
// }
//
SECTION("BlockChunk Packet Encoding") {
auto p = Packet(Packet::CHUNK_INFO);
Serializer::encodeString(p.data, gzip);
auto enetP = p.toENetPacket();
auto p2 = Packet(enetP);
int len = Serializer::decodeInt(&p2.data[0]);
std::string data(p.data.begin() + 4, p.data.begin() + 4 + len);
auto b2 = new BlockChunk();
REQUIRE(b2->deserialize(data));
for (int j = 0; j < 4096; j++) {
REQUIRE(b2->getBlock(j) == b->getBlock(j));
}
delete b2;
}
delete b;
INFO("Iteration " << i << " passed.");