1
0
Fork 0

Standalone exe & missing chunks request

This commit is contained in:
May B. 2020-09-25 20:50:46 +02:00
parent c760edb0a4
commit ba8405e51f
27 changed files with 291 additions and 87 deletions

View File

@ -22,33 +22,8 @@ add_subdirectory("include/glfw")
add_subdirectory("include/glm")
add_subdirectory("include/enet")
add_subdirectory("include/zstd")
set(LINKED_LIBS glfw pthread dl
glm::glm_static enet::enet_static zstd::zstd_static)
file(GLOB_RECURSE SOURCES "src/*/*.cpp")
file(GLOB INCLUDE_SOURCES
"include/imgui/*.cpp"
"include/FastNoiseSIMD/*.cpp"
"include/tracy/TracyClient.cpp"
"include/meshoptimizer/*.cpp"
"include/gl3w/gl3w.c"
)
set(INCLUDE_LIBS
"include/imgui"
"include/FastNoiseSIMD"
"include/toml++"
"include/robin_hood"
"include/libguarded"
"include/tracy"
"include/meshoptimizer"
"include/gl3w"
)
add_executable(univerxel "src/main.cpp" ${SOURCES} ${INCLUDE_SOURCES})
target_compile_features(univerxel PUBLIC cxx_std_17)
target_link_libraries(univerxel ${LINKED_LIBS})
target_include_directories(univerxel PRIVATE ${INCLUDE_LIBS})
target_compile_definitions(univerxel PRIVATE FIXED_WINDOW=${FIXED_WINDOW} HN_USE_FILESYSTEM=1)
add_compile_definitions(FIXED_WINDOW=${FIXED_WINDOW} HN_USE_FILESYSTEM=1)
if(PROFILING)
add_compile_definitions(TRACY_ENABLE=1)
endif(PROFILING)
@ -62,7 +37,37 @@ if(USE_FMA)
add_definitions(-mfma)
endif(USE_FMA)
file(GLOB_RECURSE CORE_SOURCES "src/core/*.cpp" "include/tracy/TracyClient.cpp")
set(CORE_HEADERS "include/toml++" "include/robin_hood" "include/libguarded" "include/tracy")
set(CORE_LIBS pthread dl glm::glm_static enet::enet_static zstd::zstd_static)
file(GLOB_RECURSE CLIENT_SOURCES "src/client/*.cpp" "include/imgui/*.cpp" "include/meshoptimizer/*.cpp" "include/gl3w/gl3w.c")
set(CLIENT_HEADERS "include/imgui" "include/meshoptimizer" "include/gl3w")
set(CLIENT_LIBS glfw)
file(GLOB_RECURSE SERVER_SOURCES "src/server/*.cpp" "include/FastNoiseSIMD/*.cpp")
set(SERVER_HEADERS "include/FastNoiseSIMD")
set(SERVER_LINKED)
# All in one exec
add_executable(univerxel "src/main.cpp" ${CORE_SOURCES} ${CLIENT_SOURCES} ${SERVER_SOURCES})
target_compile_features(univerxel PUBLIC cxx_std_17)
target_link_libraries(univerxel ${CORE_LIBS} ${CLIENT_LIBS} ${SERVER_LIBS})
target_include_directories(univerxel PRIVATE ${CORE_HEADERS} ${CLIENT_HEADERS} ${SERVER_HEADERS})
# Standalone server
add_executable(univerxel-server EXCLUDE_FROM_ALL "src/server.cpp" ${CORE_SOURCES} ${SERVER_SOURCES})
target_compile_features(univerxel-server PUBLIC cxx_std_17)
target_link_libraries(univerxel-server ${CORE_LIBS} ${SERVER_LIBS})
target_include_directories(univerxel-server PRIVATE ${CORE_HEADERS} ${SERVER_HEADERS})
# Dumb client
add_executable(univerxel-client EXCLUDE_FROM_ALL "src/client.cpp" ${CORE_SOURCES} ${CLIENT_SOURCES})
target_compile_features(univerxel-client PUBLIC cxx_std_17)
target_link_libraries(univerxel-client ${CORE_LIBS} ${CLIENT_LIBS})
target_include_directories(univerxel-client PRIVATE ${CORE_HEADERS} ${CLIENT_HEADERS})
# Resource client files + default zstd.dict
file(COPY resource/content DESTINATION ${CMAKE_BINARY_DIR})
# Docs
@ -71,14 +76,3 @@ add_custom_target(docs
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Build doc."
)
# Builtin models
file(GLOB_RECURSE MCT_SOURCES "src/model_contouring.cpp" "src/*/*.cpp")
add_executable(model_contouring EXCLUDE_FROM_ALL ${MCT_SOURCES} ${INCLUDE_SOURCES})
target_compile_features(model_contouring PUBLIC cxx_std_17)
target_link_libraries(model_contouring ${LINKED_LIBS})
target_include_directories(model_contouring PRIVATE ${INCLUDE_LIBS})
add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/content/model.ivb"
COMMAND "${CMAKE_BINARY_DIR}/model_contouring" DEPENDS model_contouring)
add_custom_target(render_models DEPENDS "${CMAKE_BINARY_DIR}/content/model.ivb")

View File

@ -14,7 +14,7 @@
- [x] Compression
- [ ] Encryption
- [x] Embedded
- [ ] Standalone
- [x] Standalone
## Hello world

View File

@ -14,12 +14,12 @@ endif()
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
option(GLFW_BUILD_EXAMPLES "Build the GLFW example programs" OFF)
option(GLFW_BUILD_TESTS "Build the GLFW test programs" OFF)
option(GLFW_BUILD_DOCS "Build the GLFW documentation" OFF)
option(GLFW_INSTALL "Generate installation target" OFF)
option(GLFW_VULKAN_STATIC "Assume the Vulkan loader is linked with the application" OFF)
set(BUILD_SHARED_LIBS OFF)
set(GLFW_BUILD_EXAMPLES OFF)
set(GLFW_BUILD_TESTS OFF)
set(GLFW_BUILD_DOCS OFF)
set(GLFW_INSTALL OFF)
set(GLFW_VULKAN_STATIC OFF)
include(GNUInstallDirs)
include(CMakeDependentOption)

View File

@ -1,3 +1,4 @@
#define TOML_HEADER_ONLY 0
//# This file is a part of toml++ and is subject to the the terms of the MIT license.
//# Copyright (c) 2019-2020 Mark Gillard <mark.gillard@outlook.com.au>
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.

40
src/client.cpp Normal file
View File

@ -0,0 +1,40 @@
/**
* \file server.cpp
* \brief Univerxel client
* \author Maelys Bois
* \version 0.0.1
*
* Univerxel standalone client program.
*/
#define STANDALONE 1
#include "client/Client.hpp"
#include "core/standalone_config.hpp"
#include "core/utils/tracy.hpp"
/// Entry point
int main(int argc, char *argv[]){
LOG("Univerxel client");
#if TRACY_ENABLE
LOG("Profiling !");
#endif
auto options = config::standalone_options<config::client::options>(argc > 1 ? argv[1] : config::DEFAULT_FILE);
options.save();
#if TRACY_ENABLE
tracy::SetThreadName("Main");
#endif
net::Setup();
auto client = Client(options.get());
client.run(nullptr);
net::Destroy();
options.save();
return 0;
}

View File

@ -180,7 +180,7 @@ void Client::run(server_handle* const localHandle) {
ZoneScopedN("Swap");
pipeline->swapBuffer(window.getPtr());
inputs.poll();
FrameMark;
FrameMarkNamed("Client");
}
window.waitTargetFPS();

View File

@ -65,7 +65,13 @@ public:
world.keepDistance = config["world"]["keep_distance"].value_or(world.keepDistance);
voxel_density = config["world"]["voxel_density"].value_or(voxel_density);
if(!config["connection"]["use_local"].value_or(true)) {
#ifndef STANDALONE
const auto useLocal = config["connection"]["use_local"].value_or(true);
#else
const auto useLocal = false;
#endif
if(!useLocal) {
world::client::Universe::connection ct;
ct.address = config["connection"]["address"].value_or(ct.address);
ct.port = config["connection"]["port"].value_or(ct.port);
@ -138,9 +144,11 @@ public:
if(connection.has_value()) {
const auto &ct = connection.value();
config.insert_or_assign("connection", toml::table({
#ifndef STANDALONE
{"use_local", false},
#endif
{"address", ct.address},
{"start_port", ct.port}
{"port", ct.port}
}));
} else {
config.insert_or_assign("connection", toml::table({{"use_local", true}}));

View File

@ -1,5 +1,6 @@
#include "UI.hpp"
#include <GL/gl3w.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include "Texture.hpp"

View File

@ -20,7 +20,7 @@ void DistantUniverse::update(voxel_pos pos, float deltaTime) {
const auto chunkChange = cur_chunk != last_chunk;
last_chunk = cur_chunk;
pullNetwork();
pullNetwork(pos);
{ // Update alive areas
ZoneScopedN("World");
@ -38,19 +38,39 @@ void DistantUniverse::update(voxel_pos pos, float deltaTime) {
} else {
if(const auto neighbors = std::dynamic_pointer_cast<world::client::EdittableChunk>(it_c->second)->update(deltaTime, true /*FIXME: rnd from contouring*/)) {
contouring->onUpdate(std::make_pair(area.first, it_c->first), diff, chunks, neighbors.value());
} else if(chunkChangeArea) {
contouring->onNotify(std::make_pair(area.first, it_c->first), diff, chunks);
}
++it_c;
}
}
if (chunkChangeArea) { // Request missing chunks
ZoneScopedN("Missing");
std::vector<chunk_pos> missing;
//TODO: use easy sphere fill
for (int x = -loadDistance; x <= loadDistance; x++) {
for (int y = -loadDistance; y <= loadDistance; y++) {
for (int z = -loadDistance; z <= loadDistance; z++) {
const auto dist2 = x * x + y * y + z * z;
if (dist2 <= loadDistance * loadDistance) {
const auto p = diff + chunk_pos(x, y, z);
if (chunks.inRange(p) && chunks.find(p) == chunks.end()) {
missing.push_back(p);
}
}
}}}
if(!missing.empty()) {
auto packet = net::Client::makePacket(net::client_packet_type::MISSING_CHUNKS, NULL, sizeof(area_id) + missing.size() * sizeof(chunk_pos), ENET_PACKET_FLAG_RELIABLE, peer.getSalt());
packet.write(area.first);
packet.write(missing.data(), missing.size() * sizeof(chunk_pos));
peer.send(packet.get(), net::channel_type::RELIABLE);
}
}
}
}
}
contouring->update(pos, areas);
}
void DistantUniverse::pullNetwork() {
void DistantUniverse::pullNetwork(voxel_pos pos) {
ZoneScopedN("Pull");
using namespace net;
@ -82,7 +102,6 @@ void DistantUniverse::pullNetwork() {
} else {
areas.emplace(id, std::make_shared<Area>(p));
}
LOG_D("Area " << id.index);
}
break;
}

View File

@ -19,7 +19,7 @@ namespace world::client {
ray_result raycast(const geometry::Ray &) const override;
protected:
void pullNetwork();
void pullNetwork(voxel_pos);
/// Alive areas containing chunks
area_map areas;

View File

@ -1,5 +1,7 @@
#include "index.hpp"
#ifndef STANDALONE
#include "LocalUniverse.hpp"
#endif
#include "DistantUniverse.hpp"
#include "../../core/utils/logger.hpp"
@ -7,12 +9,12 @@ namespace world::client {
std::unique_ptr<Universe> Load(const std::optional<Universe::connection>& ct, server_handle *const localHandle, const Universe::options &distOpts) {
if(ct.has_value()) {
return std::make_unique<DistantUniverse>(ct.value(), distOpts);
#ifndef STANDALONE
} else if(localHandle != nullptr) {
return std::make_unique<LocalUniverse>(localHandle);
#endif
} else {
if(localHandle != nullptr) {
return std::make_unique<LocalUniverse>(localHandle);
} else {
FATAL("Must enable server.allow_local or define client.connection");
}
FATAL("Must enable server.allow_local or define client.connection");
}
}
}

View File

@ -4,16 +4,16 @@
#include <fstream>
#include <filesystem>
#include <optional>
#include "../client/config.hpp"
#include "../server/config.hpp"
namespace config {
constexpr auto DEFAULT_FILE = "config.toml";
/// Savable game options
struct options {
public:
/// Load from path
options(const std::string &path = "config.toml"): path(path) {
options(const std::string &path): path(path) {
auto config = [&] {
if(std::filesystem::exists(path))
return toml::parse_file(path);
@ -51,4 +51,5 @@ private:
std::optional<server::options> _server;
std::optional<client::options> _client;
};
}

2
src/core/data/toml.cpp Normal file
View File

@ -0,0 +1,2 @@
#define TOML_HEADER_ONLY 0
#include <toml.h>

View File

@ -123,6 +123,7 @@ public:
}
constexpr bool isReady() const { return ready; }
constexpr salt_t getSalt() const { return salt; }
protected:
ENetHost *host;

View File

@ -50,6 +50,10 @@ enum class client_packet_type: enet_uint8 {
/// actions::FillCube realable
FILL_CUBE = 0,
/// Request missing chunks
/// area_id, chunk_pos[] realable
MISSING_CHUNKS = 8,
/// Position update
/// voxel_pos notify
MOVE = 16,

View File

@ -0,0 +1,44 @@
#pragma once
#include <toml.h>
#include <fstream>
#include <filesystem>
#include <optional>
namespace config {
constexpr auto DEFAULT_FILE = "config.toml";
/// Standalone options
template<typename O>
struct standalone_options {
public:
/// Load from path
standalone_options(const std::string &path): path(path) {
auto config = toml::table({{"c", [&] {
if(std::filesystem::exists(path))
return toml::parse_file(path);
LOG_E("Config file " << path << " not found. Creating default");
return toml::table();
}()}});
val = new O(config["c"]);
}
~standalone_options() {
delete val;
}
/// Write to path
void save() {
std::ofstream out;
out.open(path, std::ios::out | std::ios::trunc);
out << val->save() << "\n\n";
out.close();
}
O &get() { return *val; }
private:
std::string path;
O* val;
};
}

17
src/core/utils/tracy.hpp Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include <Tracy.hpp>
#if TRACY_MEMORY
void *operator new(std::size_t count)
{
auto ptr = malloc(count);
TracyAlloc(ptr, count);
return ptr;
}
void operator delete(void *ptr) noexcept
{
TracyFree(ptr);
free(ptr);
}
#endif

View File

@ -26,5 +26,5 @@ bool Universe::collide(const glm::ifvec3 &pos, const glm::vec3 &vel, int density
const auto dir = glm::normalize(vel);
const auto velocity = vel * glm::vec3(density);
const auto from = pos * density + dir;
return std::holds_alternative<ray_target>(raycast(Ray(from, dir, glm::length(velocity) + radius)));
return !std::holds_alternative<std::monostate>(raycast(Ray(from, dir, glm::length(velocity) + radius)));
}

View File

@ -10,22 +10,7 @@
#include "client/Client.hpp"
#include "server/Server.hpp"
#include "core/config.hpp"
#include "core/net/data.hpp"
#include <Tracy.hpp>
#if TRACY_MEMORY
void *operator new(std::size_t count)
{
auto ptr = malloc(count);
TracyAlloc(ptr, count);
return ptr;
}
void operator delete(void *ptr) noexcept
{
TracyFree(ptr);
free(ptr);
}
#endif
#include "core/utils/tracy.hpp"
/// Entry point
int main(int argc, char *argv[]){
@ -35,7 +20,7 @@ int main(int argc, char *argv[]){
LOG("Profiling !");
#endif
config::options options = config::options(argc > 1 ? argv[1] : "config.toml");
auto options = config::options(argc > 1 ? argv[1] : config::DEFAULT_FILE);
options.save();
std::optional<Server> server = options.hasServer() ? std::make_optional<Server>(options.getServer()) : std::nullopt;
@ -69,7 +54,6 @@ int main(int argc, char *argv[]){
}
} else {
if (client.has_value()) {
//MAYBE: check not local
clientTask(nullptr);
} else {
options.save();

40
src/server.cpp Normal file
View File

@ -0,0 +1,40 @@
/**
* \file server.cpp
* \brief Univerxel server
* \author Maelys Bois
* \version 0.0.1
*
* Univerxel standalone server program.
*/
#define STANDALONE 1
#include "server/Server.hpp"
#include "core/standalone_config.hpp"
#include "core/utils/tracy.hpp"
/// Entry point
int main(int argc, char *argv[]){
LOG("Univerxel server");
#if TRACY_ENABLE
LOG("Profiling !");
#endif
auto options = config::standalone_options<config::server::options>(argc > 1 ? argv[1] : config::DEFAULT_FILE);
options.save();
#if TRACY_ENABLE
tracy::SetThreadName("Main");
#endif
net::Setup();
auto server = Server(options.get());
server.run();
net::Destroy();
options.save();
return 0;
}

View File

@ -1,6 +1,8 @@
#include "Server.hpp"
#include "world/StandaloneUniverse.hpp"
#ifndef STANDALONE
#include "world/SharedUniverse.hpp"
#endif
#include <signal.h>
Server::Server(config::server::options& options): options(options) {
@ -16,8 +18,10 @@ void handle_signal(int) { running = false; }
void Server::run() {
universe = [&]() -> std::unique_ptr<world::server::Universe> {
#ifndef STANDALONE
if(options.allowLocal)
return std::make_unique<world::server::SharedUniverse>(options.world, localHandle);
#endif
return std::make_unique<world::server::StandaloneUniverse>(options.world);
}();
@ -27,6 +31,7 @@ void Server::run() {
while(running && (localHandle == nullptr || localHandle->running)) {
universe->update(1. / TPS); //FIXME: use chrono
FrameMarkNamed("Server");
std::this_thread::sleep_for(std::chrono::milliseconds(1000 / TPS));
}
}

View File

@ -7,9 +7,17 @@ namespace config::server {
struct options {
public:
options(toml::node_view<toml::node> config) {
options(toml::node_view<toml::node> config): world() {
assert(config["enabled"]);
#ifndef STANDALONE
allowLocal = config["allow_local"].value_or(allowLocal);
#else
allowLocal = false;
#endif
world.connection.address = config["connection"]["address"].value_or(world.connection.address);
world.connection.port = config["connection"]["port"].value_or(world.connection.port);
world.maxPlayers = config["max_players"].value_or(world.maxPlayers);
world.loadDistance = config["world"]["load_distance"].value_or(world.loadDistance);
world.keepDistance = config["world"]["keep_distance"].value_or(world.keepDistance);
@ -19,7 +27,14 @@ public:
toml::table save() {
return toml::table({
{"enabled", true},
#ifndef STANDALONE
{"allow_local", allowLocal},
#endif
{"connection", toml::table({
{"address", world.connection.address},
{"port", world.connection.port}
})},
{"max_players", world.maxPlayers},
{"world", toml::table({
{"load_distance", world.loadDistance},
{"keep_distance", world.keepDistance},

View File

@ -14,7 +14,7 @@ using namespace world::server;
const auto AREAS_FILE = "/areas.idx";
Universe::Universe(const Universe::options &options): host(net::connection{"localhost", 4242}, 4),
Universe::Universe(const Universe::options &options): host(options.connection, options.maxPlayers),
dict_content({options.folderPath + "/zstd.dict", "content/zstd.dict"}), dicts(dict_content), dict_write_ctx(dicts.make_writer()) {
setOptions(options);
folderPath = options.folderPath;
@ -314,7 +314,8 @@ void Universe::update(float deltaTime) {
it->second->setChunks().emplace(loaded.first.second, loaded.second);
const chunk_pos diff = glm::divide(pos - it->second->getOffset().as_voxel());
loadChunk(loaded.first, diff, it->second->getChunks());
broadcastChunk(loaded);
// MAYBE: limit chunks per update
host.broadcast(serializeChunk(loaded), net::channel_type::RELIABLE);
}
}
}
@ -375,6 +376,29 @@ void Universe::pullNetwork() {
}
break;
}
case client_packet_type::MISSING_CHUNKS: {
const auto pos = entities.at(PLAYER_ENTITY_ID).instances.at(Server::GetPeerData<net_client>(peer)->instanceId).pos.as_voxel();
auto reader = PacketReader(packet);
area_id id = *reader.read<area_id>();
if(auto area = areas.find(id); area != areas.end()) {
auto &chunks = area->second->getChunks();
const chunk_pos diff = glm::divide(pos - area->second->getOffset().as_voxel());
while(!reader.isFull()) {
chunk_pos cpos = *reader.read<chunk_pos>();
if(glm::length2(diff - cpos) <= glm::pow2(loadDistance) && chunks.inRange(cpos)) {
if(auto chunk = chunks.find(cpos); chunk != chunks.end()) {
host.send(peer, serializeChunk({std::make_pair(id, cpos), std::dynamic_pointer_cast<Chunk>(chunk->second)}), net::channel_type::RELIABLE);
}
} else {
LOG_D("Request out of range chunk");
}
}
} else {
LOG_D("Bad chunk request");
}
break;
}
default:
LOG_D("Bad packet from " << peer->address);
break;
@ -394,8 +418,7 @@ void Universe::broadcastAreas() {
assert(packet.isFull());
host.broadcast(packet.get(), net::channel_type::RELIABLE);
}
void Universe::broadcastChunk(const robin_hood::pair<area_<chunk_pos>, std::shared_ptr<Chunk>> &pair) {
// MAYBE: limit chunks per update
net::packet_t* Universe::serializeChunk(const robin_hood::pair<area_<chunk_pos>, std::shared_ptr<Chunk>> &pair) {
std::ostringstream out;
pair.second->write(out);
std::vector<char> buffer;
@ -404,7 +427,7 @@ void Universe::broadcastChunk(const robin_hood::pair<area_<chunk_pos>, std::shar
packet.write(pair.first);
packet.write(buffer.data(), buffer.size());
assert(packet.isFull());
host.broadcast(packet.get(), net::channel_type::RELIABLE);
return packet.get();
}
void Universe::updateChunk(area_map::iterator &, world::ChunkContainer::iterator &, chunk_pos, float deltaTime) {}

View File

@ -26,6 +26,9 @@ namespace world::server {
struct options: world::Universe::options {
/// Storage path
std::string folderPath = "world";
net::connection connection = net::connection{"localhost", 4242};
int maxPlayers = 1;
};
Universe(const options &);
@ -64,7 +67,7 @@ namespace world::server {
/// Handle networking requests
void pullNetwork();
void broadcastAreas();
void broadcastChunk(const robin_hood::pair<area_<chunk_pos>, std::shared_ptr<Chunk>> &);
net::packet_t* serializeChunk(const robin_hood::pair<area_<chunk_pos>, std::shared_ptr<Chunk>> &);
using area_map = robin_hood::unordered_map<area_id, std::shared_ptr<Area>>;