From c973988ad1c45333a8c280820b2d7a8d71e088d8 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Fri, 28 Aug 2015 16:09:30 +0200 Subject: [PATCH 01/16] give feedback to stdout when profiling --- src/app/app.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/app/app.cpp b/src/app/app.cpp index 610f995..0e2a639 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -18,6 +18,7 @@ #include "../world/Entity.hpp" #include +#include #include #include #include @@ -63,6 +64,12 @@ void Application::RunT(size_t t) { void Application::RunS(size_t n, size_t t) { for (size_t i = 0; HasState() && i < n; ++i) { Loop(t); + std::cout << '.'; + if (i % 16 == 15) { + std::cout << std::setfill(' ') << std::setw(5) << std::right << (i + 1) << std::endl; + } else { + std::cout << std::flush; + } } } -- 2.39.2 From f27b8bb27fa87487bb5d29a1456e610255287b04 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Mon, 31 Aug 2015 13:49:38 +0200 Subject: [PATCH 02/16] add minimal debug symbols in release build this should make tracing core dumps much easier and they can always be stripped anyway --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 36fd021..7bafccd 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ LDXXFLAGS += $(PKGLIBS) DEBUG_FLAGS = -g3 -O0 PROFILE_FLAGS = -DNDEBUG -O1 -g3 -RELEASE_FLAGS = -DNDEBUG -O2 +RELEASE_FLAGS = -DNDEBUG -O2 -g1 TEST_FLAGS = -g -O2 -I./src $(TESTFLAGS) SOURCE_DIR := src -- 2.39.2 From 9ebe2c320fd9f94266ab93fa2f9d9908a0a284d3 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Wed, 2 Sep 2015 17:27:01 +0200 Subject: [PATCH 03/16] first draft for client/server architecture --- Makefile | 5 +- building | 9 +- running | 18 +++ src/app/Application.hpp | 51 ++++++-- src/app/Assets.hpp | 12 +- src/app/ClientState.cpp | 42 ++++++ src/app/ClientState.hpp | 39 ++++++ src/app/Environment.hpp | 25 +++- src/app/Runtime.hpp | 20 +++ src/app/ServerState.cpp | 46 +++++++ src/app/ServerState.hpp | 39 ++++++ src/app/State.hpp | 4 +- src/app/StateControl.hpp | 4 +- src/app/WorldState.cpp | 6 +- src/app/app.cpp | 97 ++++++++++---- src/app/init.cpp | 55 +++++++- src/app/init.hpp | 44 ++++++- src/app/runtime.cpp | 112 ++++++++++++++-- src/net/Client.hpp | 45 +++++++ src/net/Connection.hpp | 42 ++++++ src/net/Packet.hpp | 33 +++++ src/net/Server.hpp | 47 +++++++ src/net/io.hpp | 13 ++ src/net/net.cpp | 247 ++++++++++++++++++++++++++++++++++++ src/ui/ui.cpp | 4 +- src/world/ChunkRenderer.hpp | 4 +- src/world/render.cpp | 4 +- 27 files changed, 991 insertions(+), 76 deletions(-) create mode 100644 src/app/ClientState.cpp create mode 100644 src/app/ClientState.hpp create mode 100644 src/app/ServerState.cpp create mode 100644 src/app/ServerState.hpp create mode 100644 src/net/Client.hpp create mode 100644 src/net/Connection.hpp create mode 100644 src/net/Packet.hpp create mode 100644 src/net/Server.hpp create mode 100644 src/net/io.hpp create mode 100644 src/net/net.cpp diff --git a/Makefile b/Makefile index 7bafccd..e6e24d9 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CXX = g++ --std=c++11 LDXX = g++ -LIBS = sdl2 SDL2_image SDL2_ttf glew openal freealut zlib +LIBS = sdl2 SDL2_image SDL2_net SDL2_ttf glew openal freealut zlib PKGFLAGS := $(shell pkg-config --cflags $(LIBS)) PKGLIBS := $(shell pkg-config --libs $(LIBS)) @@ -64,6 +64,9 @@ tests: $(TEST_BIN) run: $(ASSET_DEP) blank ./blank --save-path saves/ +server: $(ASSET_DEP) blank + ./blank --server --save-path saves/ + gdb: $(ASSET_DEP) blank.debug gdb ./blank.debug diff --git a/building b/building index bc22d9b..ef83775 100644 --- a/building +++ b/building @@ -1,11 +1,11 @@ Dependencies ============ - GLEW, GLM, SDL2, SDL2_image, SDL2_ttf, OpenAL, freealut, zlib + GLEW, GLM, SDL2, SDL2_image, SDL2_net, SDL2_ttf, OpenAL, freealut, zlib CppUnit for tests -archlinux: pacman -S glew glm sdl2 sdl2_image sdl2_ttf openal freealut zlib cppunit +archlinux: pacman -S glew glm sdl2 sdl2_image sdl2_net sdl2_ttf openal freealut zlib cppunit manual: CppUnit http://sourceforge.net/projects/cppunit/ @@ -29,7 +29,10 @@ release (default), debug, profile: build executables tuned for running, debugging, and profiling run: - build and execute the main binary + build and execute the main binary with state path set to ./saves + +server: + same as run, only in server mode test: build and run unittests diff --git a/running b/running index 8638f43..7c089ff 100644 --- a/running +++ b/running @@ -34,6 +34,15 @@ Application --no-vsync disable vsync +--standalone + run as standalone (the default) + +--client + run as client + +--server + run as server + Interface --------- @@ -51,6 +60,15 @@ Interface the audio device and sounds will still be allocated it just stops the interface from queueing buffers +Network +------- + +--host + hostname to connect to in client mode + +--port + port number to connection to (client) or listen on (server) + World ----- diff --git a/src/app/Application.hpp b/src/app/Application.hpp index bae8435..359599e 100644 --- a/src/app/Application.hpp +++ b/src/app/Application.hpp @@ -8,22 +8,27 @@ namespace blank { class Environment; +class HeadlessEnvironment; class State; class Window; -class Application { +class HeadlessApplication { public: - explicit Application(Environment &); - ~Application(); + explicit HeadlessApplication(HeadlessEnvironment &); + ~HeadlessApplication(); - Application(const Application &) = delete; - Application &operator =(const Application &) = delete; + void PushState(State *); + State *PopState(); + State *SwitchState(State *); + State &GetState(); + void CommitStates(); + bool HasState() const noexcept; - /// run until user quits + /// run until out of states void Run(); /// evaluate a single frame of dt milliseconds - void Loop(int dt); + virtual void Loop(int dt); /// run for n frames void RunN(size_t n); @@ -32,6 +37,31 @@ public: /// run for n frames, assuming t milliseconds for each void RunS(size_t n, size_t t); + /// process all events in SDL's queue + void HandleEvents(); + void Handle(const SDL_Event &); + /// integrate to the next step with dt milliseconds passed + void Update(int dt); + +private: + HeadlessEnvironment &env; + std::stack states; + +}; + + +class Application +: public HeadlessApplication { + +public: + explicit Application(Environment &); + ~Application(); + + Application(const Application &) = delete; + Application &operator =(const Application &) = delete; + + void Loop(int dt) override; + /// process all events in SDL's queue void HandleEvents(); void Handle(const SDL_Event &); @@ -41,15 +71,8 @@ public: /// push the current state to display void Render(); - void PushState(State *); - State *PopState(); - State *SwitchState(State *); - State &GetState(); - bool HasState() const noexcept; - private: Environment &env; - std::stack states; }; diff --git a/src/app/Assets.hpp b/src/app/Assets.hpp index 65c72fe..d1a4ae7 100644 --- a/src/app/Assets.hpp +++ b/src/app/Assets.hpp @@ -14,10 +14,10 @@ class Sound; class Texture; class TextureIndex; -class Assets { +class AssetLoader { public: - explicit Assets(const std::string &base); + explicit AssetLoader(const std::string &base); void LoadBlockTypes(const std::string &set_name, BlockTypeRegistry &, TextureIndex &) const; Font LoadFont(const std::string &name, int size) const; @@ -32,11 +32,15 @@ private: std::string textures; std::string data; -public: - // common assets shared by may states +}; + +struct Assets { + Font large_ui_font; Font small_ui_font; + Assets(const AssetLoader &); + }; } diff --git a/src/app/ClientState.cpp b/src/app/ClientState.cpp new file mode 100644 index 0000000..c371235 --- /dev/null +++ b/src/app/ClientState.cpp @@ -0,0 +1,42 @@ +#include "ClientState.hpp" + +#include "Environment.hpp" +#include "TextureIndex.hpp" + +namespace blank { + +ClientState::ClientState( + Environment &env, + const World::Config &wc, + const WorldSave &ws, + const Client::Config &cc +) +: env(env) +, block_types() +, world(block_types, wc, ws) +, client(cc, world) { + +} + + +void ClientState::Handle(const SDL_Event &event) { + if (event.type == SDL_QUIT) { + env.state.PopAll(); + } +} + + +void ClientState::Update(int dt) { + client.Handle(); + client.Update(dt); + if (client.TimedOut()) { + env.state.Pop(); + } +} + + +void ClientState::Render(Viewport &viewport) { + +} + +} diff --git a/src/app/ClientState.hpp b/src/app/ClientState.hpp new file mode 100644 index 0000000..f54e56f --- /dev/null +++ b/src/app/ClientState.hpp @@ -0,0 +1,39 @@ +#ifndef BLANK_APP_CLIENTSTATE_HPP_ +#define BLANK_APP_CLIENTSTATE_HPP_ + +#include "State.hpp" +#include "../net/Client.hpp" +#include "../world/BlockTypeRegistry.hpp" +#include "../world/World.hpp" + + +namespace blank { + +class Environment; + +class ClientState +: public State { + +public: + ClientState( + Environment &, + const World::Config &, + const WorldSave &, + const Client::Config & + ); + + void Handle(const SDL_Event &) override; + void Update(int dt) override; + void Render(Viewport &) override; + +private: + Environment &env; + BlockTypeRegistry block_types; + World world; + Client client; + +}; + +} + +#endif diff --git a/src/app/Environment.hpp b/src/app/Environment.hpp index 4c935d8..95813fc 100644 --- a/src/app/Environment.hpp +++ b/src/app/Environment.hpp @@ -15,20 +15,33 @@ namespace blank { class Window; -struct Environment { +struct HeadlessEnvironment { + + AssetLoader loader; + + FrameCounter counter; + + StateControl state; + + + explicit HeadlessEnvironment(const std::string &asset_path); + +}; + + +struct Environment +: public HeadlessEnvironment { + + Assets assets; Audio audio; Viewport viewport; Window &window; - Assets assets; Keymap keymap; - FrameCounter counter; - - StateControl state; - explicit Environment(Window &win, const std::string &asset_path); + Environment(Window &win, const std::string &asset_path); }; diff --git a/src/app/Runtime.hpp b/src/app/Runtime.hpp index 81c015c..ee43853 100644 --- a/src/app/Runtime.hpp +++ b/src/app/Runtime.hpp @@ -1,6 +1,8 @@ #ifndef BLANK_RUNTIME_HPP_ #define BLANK_RUNTIME_HPP_ +#include "../net/Client.hpp" +#include "../net/Server.hpp" #include "../ui/Interface.hpp" #include "../world/World.hpp" @@ -10,6 +12,8 @@ namespace blank { +class HeadlessApplication; + /// Parse and interpret arguemnts, then set up the environment and execute. class Runtime { @@ -27,6 +31,12 @@ public: ERROR, }; + enum Target { + STANDALONE, + SERVER, + CLIENT, + }; + struct Config { bool vsync = true; bool doublebuf = true; @@ -36,7 +46,9 @@ public: std::string save_path; std::string world_name = "default"; + Client::Config client = Client::Config(); Interface::Config interface = Interface::Config(); + Server::Config server = Server::Config(); World::Config world = World::Config(); }; @@ -46,9 +58,17 @@ public: int Execute(); +private: + void RunStandalone(); + void RunServer(); + void RunClient(); + + void Run(HeadlessApplication &); + private: const char *name; Mode mode; + Target target; std::size_t n; std::size_t t; Config config; diff --git a/src/app/ServerState.cpp b/src/app/ServerState.cpp new file mode 100644 index 0000000..d26f552 --- /dev/null +++ b/src/app/ServerState.cpp @@ -0,0 +1,46 @@ +#include "ServerState.hpp" + +#include "Environment.hpp" +#include "TextureIndex.hpp" +#include "../net/io.hpp" + +#include + + +namespace blank { + +ServerState::ServerState( + HeadlessEnvironment &env, + const World::Config &wc, + const WorldSave &ws, + const Server::Config &sc +) +: env(env) +, block_types() +, world(block_types, wc, ws) +, server(sc, world) { + TextureIndex tex_index; + env.loader.LoadBlockTypes("default", block_types, tex_index); + + std::cout << "listening on UDP port " << sc.port << std::endl; +} + + +void ServerState::Handle(const SDL_Event &event) { + if (event.type == SDL_QUIT) { + env.state.PopAll(); + } +} + + +void ServerState::Update(int dt) { + server.Handle(); + server.Update(dt); +} + + +void ServerState::Render(Viewport &viewport) { + +} + +} diff --git a/src/app/ServerState.hpp b/src/app/ServerState.hpp new file mode 100644 index 0000000..fc9ac19 --- /dev/null +++ b/src/app/ServerState.hpp @@ -0,0 +1,39 @@ +#ifndef BLANK_APP_SERVERSTATE_HPP_ +#define BLANK_APP_SERVERSTATE_HPP_ + +#include "State.hpp" +#include "../net/Server.hpp" +#include "../world/BlockTypeRegistry.hpp" +#include "../world/World.hpp" + + +namespace blank { + +class HeadlessEnvironment; + +class ServerState +: public State { + +public: + ServerState( + HeadlessEnvironment &, + const World::Config &, + const WorldSave &, + const Server::Config & + ); + + void Handle(const SDL_Event &) override; + void Update(int dt) override; + void Render(Viewport &) override; + +private: + HeadlessEnvironment &env; + BlockTypeRegistry block_types; + World world; + Server server; + +}; + +} + +#endif diff --git a/src/app/State.hpp b/src/app/State.hpp index 86ff0ad..f79bc60 100644 --- a/src/app/State.hpp +++ b/src/app/State.hpp @@ -6,12 +6,12 @@ namespace blank { -class Application; +class HeadlessApplication; class Viewport; struct State { - friend class Application; + friend class HeadlessApplication; virtual void Handle(const SDL_Event &) = 0; diff --git a/src/app/StateControl.hpp b/src/app/StateControl.hpp index 818bce5..c072040 100644 --- a/src/app/StateControl.hpp +++ b/src/app/StateControl.hpp @@ -6,7 +6,7 @@ namespace blank { -class Application; +class HeadlessApplication; class State; class StateControl { @@ -29,7 +29,7 @@ public: } - void Commit(Application &); + void Commit(HeadlessApplication &); private: enum Command { diff --git a/src/app/WorldState.cpp b/src/app/WorldState.cpp index 243b0a1..03abe36 100644 --- a/src/app/WorldState.cpp +++ b/src/app/WorldState.cpp @@ -1,6 +1,7 @@ #include "WorldState.hpp" #include "Environment.hpp" +#include "init.hpp" #include "TextureIndex.hpp" #include @@ -23,8 +24,8 @@ WorldState::WorldState( , preload(env, world.Loader(), chunk_renderer) , unload(env, world.Loader()) { TextureIndex tex_index; - env.assets.LoadBlockTypes("default", block_types, tex_index); - chunk_renderer.LoadTextures(env.assets, tex_index); + env.loader.LoadBlockTypes("default", block_types, tex_index); + chunk_renderer.LoadTextures(env.loader, tex_index); chunk_renderer.FogDensity(wc.fog_density); // TODO: better solution for initializing HUD interface.SelectNext(); @@ -33,6 +34,7 @@ WorldState::WorldState( void WorldState::OnEnter() { env.state.Push(&preload); + env.window.GrabMouse(); } diff --git a/src/app/app.cpp b/src/app/app.cpp index 0e2a639..48a5950 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -29,18 +29,29 @@ using std::string; namespace blank { -Application::Application(Environment &e) +HeadlessApplication::HeadlessApplication(HeadlessEnvironment &e) : env(e) , states() { } +HeadlessApplication::~HeadlessApplication() { + +} + + +Application::Application(Environment &e) +: HeadlessApplication(e) +, env(e) { + +} + Application::~Application() { env.audio.StopAll(); } -void Application::RunN(size_t n) { +void HeadlessApplication::RunN(size_t n) { Uint32 last = SDL_GetTicks(); for (size_t i = 0; HasState() && i < n; ++i) { Uint32 now = SDL_GetTicks(); @@ -50,7 +61,7 @@ void Application::RunN(size_t n) { } } -void Application::RunT(size_t t) { +void HeadlessApplication::RunT(size_t t) { Uint32 last = SDL_GetTicks(); Uint32 finish = last + t; while (HasState() && last < finish) { @@ -61,7 +72,7 @@ void Application::RunT(size_t t) { } } -void Application::RunS(size_t n, size_t t) { +void HeadlessApplication::RunS(size_t n, size_t t) { for (size_t i = 0; HasState() && i < n; ++i) { Loop(t); std::cout << '.'; @@ -74,9 +85,8 @@ void Application::RunS(size_t n, size_t t) { } -void Application::Run() { +void HeadlessApplication::Run() { Uint32 last = SDL_GetTicks(); - env.window.GrabMouse(); while (HasState()) { Uint32 now = SDL_GetTicks(); int delta = now - last; @@ -85,24 +95,47 @@ void Application::Run() { } } +void HeadlessApplication::Loop(int dt) { + env.counter.EnterFrame(); + Update(dt); + CommitStates(); + if (!HasState()) return; + env.counter.ExitFrame(); +} + void Application::Loop(int dt) { env.counter.EnterFrame(); HandleEvents(); if (!HasState()) return; Update(dt); - env.state.Commit(*this); + CommitStates(); if (!HasState()) return; Render(); env.counter.ExitFrame(); } +void HeadlessApplication::HandleEvents() { + env.counter.EnterHandle(); + SDL_Event event; + while (HasState() && SDL_PollEvent(&event)) { + Handle(event); + CommitStates(); + } + env.counter.ExitHandle(); +} + +void HeadlessApplication::Handle(const SDL_Event &event) { + GetState().Handle(event); +} + + void Application::HandleEvents() { env.counter.EnterHandle(); SDL_Event event; while (HasState() && SDL_PollEvent(&event)) { Handle(event); - env.state.Commit(*this); + CommitStates(); } env.counter.ExitHandle(); } @@ -134,6 +167,14 @@ void Application::Handle(const SDL_WindowEvent &event) { } } +void HeadlessApplication::Update(int dt) { + env.counter.EnterUpdate(); + if (HasState()) { + GetState().Update(dt); + } + env.counter.ExitUpdate(); +} + void Application::Update(int dt) { env.counter.EnterUpdate(); env.audio.Update(dt); @@ -158,7 +199,7 @@ void Application::Render() { } -void Application::PushState(State *s) { +void HeadlessApplication::PushState(State *s) { if (!states.empty()) { states.top()->OnPause(); } @@ -170,7 +211,7 @@ void Application::PushState(State *s) { s->OnResume(); } -State *Application::PopState() { +State *HeadlessApplication::PopState() { State *s = states.top(); states.pop(); s->OnPause(); @@ -181,7 +222,7 @@ State *Application::PopState() { return s; } -State *Application::SwitchState(State *s_new) { +State *HeadlessApplication::SwitchState(State *s_new) { State *s_old = states.top(); states.top() = s_new; --s_old->ref_count; @@ -197,16 +238,20 @@ State *Application::SwitchState(State *s_new) { return s_old; } -State &Application::GetState() { +State &HeadlessApplication::GetState() { return *states.top(); } -bool Application::HasState() const noexcept { +void HeadlessApplication::CommitStates() { + env.state.Commit(*this); +} + +bool HeadlessApplication::HasState() const noexcept { return !states.empty(); } -void StateControl::Commit(Application &app) { +void StateControl::Commit(HeadlessApplication &app) { while (!cue.empty()) { Memo m(cue.front()); cue.pop(); @@ -230,13 +275,17 @@ void StateControl::Commit(Application &app) { } -Assets::Assets(const string &base) +AssetLoader::AssetLoader(const string &base) : fonts(base + "fonts/") , sounds(base + "sounds/") , textures(base + "textures/") -, data(base + "data/") -, large_ui_font(LoadFont("DejaVuSans", 24)) -, small_ui_font(LoadFont("DejaVuSans", 16)) { +, data(base + "data/") { + +} + +Assets::Assets(const AssetLoader &loader) +: large_ui_font(loader.LoadFont("DejaVuSans", 24)) +, small_ui_font(loader.LoadFont("DejaVuSans", 16)) { } @@ -248,7 +297,7 @@ CuboidShape slab_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.0f, 0.5f }}); } -void Assets::LoadBlockTypes(const std::string &set_name, BlockTypeRegistry ®, TextureIndex &tex_index) const { +void AssetLoader::LoadBlockTypes(const std::string &set_name, BlockTypeRegistry ®, TextureIndex &tex_index) const { string full = data + set_name + ".types"; std::ifstream file(full); if (!file) { @@ -314,17 +363,17 @@ void Assets::LoadBlockTypes(const std::string &set_name, BlockTypeRegistry ®, } } -Font Assets::LoadFont(const string &name, int size) const { +Font AssetLoader::LoadFont(const string &name, int size) const { string full = fonts + name + ".ttf"; return Font(full.c_str(), size); } -Sound Assets::LoadSound(const string &name) const { +Sound AssetLoader::LoadSound(const string &name) const { string full = sounds + name + ".wav"; return Sound(full.c_str()); } -Texture Assets::LoadTexture(const string &name) const { +Texture AssetLoader::LoadTexture(const string &name) const { string full = textures + name + ".png"; Texture tex; SDL_Surface *srf = IMG_Load(full.c_str()); @@ -337,7 +386,7 @@ Texture Assets::LoadTexture(const string &name) const { return tex; } -void Assets::LoadTexture(const string &name, ArrayTexture &tex, int layer) const { +void AssetLoader::LoadTexture(const string &name, ArrayTexture &tex, int layer) const { string full = textures + name + ".png"; SDL_Surface *srf = IMG_Load(full.c_str()); if (!srf) { @@ -353,7 +402,7 @@ void Assets::LoadTexture(const string &name, ArrayTexture &tex, int layer) const SDL_FreeSurface(srf); } -void Assets::LoadTextures(const TextureIndex &index, ArrayTexture &tex) const { +void AssetLoader::LoadTextures(const TextureIndex &index, ArrayTexture &tex) const { // TODO: where the hell should that size come from? tex.Reserve(16, 16, index.Size(), Format()); for (const auto &entry : index.Entries()) { diff --git a/src/app/init.cpp b/src/app/init.cpp index 18816a4..81c2e92 100644 --- a/src/app/init.cpp +++ b/src/app/init.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -20,6 +21,15 @@ std::string sdl_error_append(std::string msg) { return msg; } +std::string net_error_append(std::string msg) { + const char *error = SDLNet_GetError(); + if (*error != '\0') { + msg += ": "; + msg += error; + } + return msg; +} + std::string alut_error_append(ALenum num, std::string msg) { const char *error = alutGetErrorString(num); if (*error != '\0') { @@ -44,6 +54,17 @@ AlutError::AlutError(ALenum num, const std::string &msg) } +NetError::NetError() +: std::runtime_error(SDLNet_GetError()) { + +} + +NetError::NetError(const std::string &msg) +: std::runtime_error(net_error_append(msg)) { + +} + + SDLError::SDLError() : std::runtime_error(SDL_GetError()) { @@ -56,8 +77,8 @@ SDLError::SDLError(const std::string &msg) InitSDL::InitSDL() { - if (SDL_Init(SDL_INIT_VIDEO) != 0) { - throw SDLError("SDL_Init(SDL_INIT_VIDEO)"); + if (SDL_Init(0) != 0) { + throw SDLError("SDL_Init(0)"); } } @@ -66,6 +87,17 @@ InitSDL::~InitSDL() { } +InitVideo::InitVideo() { + if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { + throw SDLError("SDL_InitSubSystem(SDL_INIT_VIDEO)"); + } +} + +InitVideo::~InitVideo() { + SDL_QuitSubSystem(SDL_INIT_VIDEO); +} + + InitIMG::InitIMG() { if (IMG_Init(IMG_INIT_PNG) == 0) { throw SDLError("IMG_Init(IMG_INIT_PNG)"); @@ -77,6 +109,17 @@ InitIMG::~InitIMG() { } +InitNet::InitNet() { + if (SDLNet_Init() != 0) { + throw SDLError("SDLNet_Init()"); + } +} + +InitNet::~InitNet() { + SDLNet_Quit(); +} + + InitTTF::InitTTF() { if (TTF_Init() != 0) { throw SDLError("TTF_Init()"); @@ -198,8 +241,14 @@ InitGLEW::InitGLEW() { } -Init::Init(bool double_buffer, int sample_size) +InitHeadless::InitHeadless() : init_sdl() +, init_net() { + +} + +Init::Init(bool double_buffer, int sample_size) +: init_video() , init_img() , init_ttf() , init_gl(double_buffer, sample_size) diff --git a/src/app/init.hpp b/src/app/init.hpp index 13bffa1..66c3345 100644 --- a/src/app/init.hpp +++ b/src/app/init.hpp @@ -27,6 +27,15 @@ public: }; +class NetError +: public std::runtime_error { + +public: + NetError(); + explicit NetError(const std::string &); + +}; + class InitSDL { @@ -40,6 +49,18 @@ public: }; +class InitVideo { + +public: + InitVideo(); + ~InitVideo(); + + InitVideo(const InitVideo &) = delete; + InitVideo &operator =(const InitVideo &) = delete; + +}; + + class InitIMG { public: @@ -52,6 +73,18 @@ public: }; +class InitNet { + +public: + InitNet(); + ~InitNet(); + + InitNet(const InitNet &) = delete; + InitNet &operator =(const InitNet &) = delete; + +}; + + class InitTTF { public: @@ -138,11 +171,20 @@ public: }; +struct InitHeadless { + + InitHeadless(); + + InitSDL init_sdl; + InitNet init_net; + +}; + struct Init { Init(bool double_buffer = true, int sample_size = 1); - InitSDL init_sdl; + InitVideo init_video; InitIMG init_img; InitTTF init_ttf; InitAL init_al; diff --git a/src/app/runtime.cpp b/src/app/runtime.cpp index d77cf47..2f0f82e 100644 --- a/src/app/runtime.cpp +++ b/src/app/runtime.cpp @@ -1,6 +1,8 @@ #include "Application.hpp" +#include "ClientState.hpp" #include "Environment.hpp" #include "Runtime.hpp" +#include "ServerState.hpp" #include "WorldState.hpp" #include "init.hpp" @@ -45,12 +47,20 @@ string default_save_path() { namespace blank { +HeadlessEnvironment::HeadlessEnvironment(const string &asset_path) +: loader(asset_path) +, counter() +, state() { + +} + Environment::Environment(Window &win, const string &asset_path) -: audio() +: HeadlessEnvironment(asset_path) +, assets(loader) +, audio() , viewport() , window(win) -, assets(asset_path) -, counter() { +, keymap() { viewport.Clear(); window.Flip(); keymap.LoadDefault(); @@ -60,6 +70,7 @@ Environment::Environment(Window &win, const string &asset_path) Runtime::Runtime() noexcept : name("blank") , mode(NORMAL) +, target(STANDALONE) , n(0) , t(0) , config() { @@ -100,6 +111,12 @@ void Runtime::ReadArgs(int argc, const char *const *argv) { config.interface.visual_disabled = true; } else if (strcmp(param, "no-audio") == 0) { config.interface.audio_disabled = true; + } else if (strcmp(param, "standalone") == 0) { + target = STANDALONE; + } else if (strcmp(param, "server") == 0) { + target = SERVER; + } else if (strcmp(param, "client") == 0) { + target = CLIENT; } else if (strcmp(param, "asset-path") == 0) { ++i; if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') { @@ -108,6 +125,23 @@ void Runtime::ReadArgs(int argc, const char *const *argv) { } else { config.asset_path = argv[i]; } + } else if (strcmp(param, "host") == 0) { + ++i; + if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') { + cerr << "missing argument to --host" << endl; + error = true; + } else { + config.client.host = argv[i]; + } + } else if (strcmp(param, "port") == 0) { + ++i; + if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') { + cerr << "missing argument to --port" << endl; + error = true; + } else { + config.server.port = strtoul(argv[i], nullptr, 10); + config.client.port = config.server.port; + } } else if (strcmp(param, "save-path") == 0) { ++i; if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') { @@ -230,11 +264,37 @@ int Runtime::Execute() { return 1; } + InitHeadless init_headless; + + switch (target) { + default: + case STANDALONE: + RunStandalone(); + break; + case SERVER: + RunServer(); + break; + case CLIENT: + RunClient(); + break; + } + + return 0; +} + +void Runtime::RunStandalone() { Init init(config.doublebuf, config.multisampling); Environment env(init.window, config.asset_path); env.viewport.VSync(config.vsync); + WorldSave save(config.save_path + config.world_name + '/'); + if (save.Exists()) { + save.Read(config.world); + } else { + save.Write(config.world); + } + std::string keys_path = config.save_path + "keys.conf"; if (!is_file(keys_path)) { std::ofstream file(keys_path); @@ -244,6 +304,33 @@ int Runtime::Execute() { env.keymap.Load(file); } + Application app(env); + WorldState world_state(env, config.interface, config.world, save); + app.PushState(&world_state); + Run(app); +} + +void Runtime::RunServer() { + HeadlessEnvironment env(config.asset_path); + + WorldSave save(config.save_path + config.world_name + '/'); + if (save.Exists()) { + save.Read(config.world); + } else { + save.Write(config.world); + } + + HeadlessApplication app(env); + ServerState server_state(env, config.world, save, config.server); + app.PushState(&server_state); + Run(app); +} + +void Runtime::RunClient() { + Init init(config.doublebuf, config.multisampling); + + Environment env(init.window, config.asset_path); + env.viewport.VSync(config.vsync); WorldSave save(config.save_path + config.world_name + '/'); if (save.Exists()) { @@ -252,11 +339,22 @@ int Runtime::Execute() { save.Write(config.world); } - Application app(env); + std::string keys_path = config.save_path + "keys.conf"; + if (!is_file(keys_path)) { + std::ofstream file(keys_path); + env.keymap.Save(file); + } else { + std::ifstream file(keys_path); + env.keymap.Load(file); + } - WorldState world_state(env, config.interface, config.world, save); - app.PushState(&world_state); + Application app(env); + ClientState client_state(env, config.world, save, config.client); + app.PushState(&client_state); + Run(app); +} +void Runtime::Run(HeadlessApplication &app) { switch (mode) { default: case NORMAL: @@ -272,8 +370,6 @@ int Runtime::Execute() { app.RunS(n, t); break; } - - return 0; } } diff --git a/src/net/Client.hpp b/src/net/Client.hpp new file mode 100644 index 0000000..63c3fdd --- /dev/null +++ b/src/net/Client.hpp @@ -0,0 +1,45 @@ +#ifndef BLANK_NET_CLIENT_HPP_ +#define BLANK_NET_CLIENT_HPP_ + +#include "Connection.hpp" + +#include +#include + + +namespace blank { + +class World; + +class Client { + +public: + struct Config { + std::string host = "localhost"; + Uint16 port = 12354; + }; + +public: + Client(const Config &, World &); + ~Client(); + + void Handle(); + + void Update(int dt); + + bool TimedOut() { return conn.TimedOut(); } + +private: + void HandlePacket(const UDPpacket &); + +private: + World &world; + Connection conn; + UDPsocket client_sock; + UDPpacket client_pack; + +}; + +} + +#endif diff --git a/src/net/Connection.hpp b/src/net/Connection.hpp new file mode 100644 index 0000000..49e9cda --- /dev/null +++ b/src/net/Connection.hpp @@ -0,0 +1,42 @@ +#ifndef BLANK_NET_CONNECTION_HPP_ +#define BLANK_NET_CONNECTION_HPP_ + +#include "../app/IntervalTimer.hpp" + +#include + + +namespace blank { + +class Connection { + +public: + explicit Connection(const IPaddress &); + + const IPaddress &Address() const noexcept { return addr; } + + bool Matches(const IPaddress &) const noexcept; + + void FlagSend() noexcept; + void FlagRecv() noexcept; + + bool ShouldPing() const noexcept; + bool TimedOut() const noexcept; + + void Update(int dt); + + + void SendPing(UDPpacket &, UDPsocket); + + void Send(UDPpacket &, UDPsocket); + +private: + IPaddress addr; + IntervalTimer send_timer; + IntervalTimer recv_timer; + +}; + +} + +#endif diff --git a/src/net/Packet.hpp b/src/net/Packet.hpp new file mode 100644 index 0000000..7b482e5 --- /dev/null +++ b/src/net/Packet.hpp @@ -0,0 +1,33 @@ +#ifndef BLANK_NET_PACKET_HPP_ +#define BLANK_NET_PACKET_HPP_ + +#include + + +namespace blank { + +struct Packet { + + static constexpr std::uint32_t TAG = 0xFB1AB1AF; + + enum Type { + PING, + }; + + struct Header { + std::uint32_t tag; + std::uint8_t type; + } header; + + std::uint8_t payload[500 - sizeof(Header)]; + + + void Tag() noexcept; + + std::size_t Ping() noexcept; + +}; + +} + +#endif diff --git a/src/net/Server.hpp b/src/net/Server.hpp new file mode 100644 index 0000000..b6a72a9 --- /dev/null +++ b/src/net/Server.hpp @@ -0,0 +1,47 @@ +#ifndef BLANK_NET_SERVER_HPP +#define BLANK_NET_SERVER_HPP + +#include +#include + + +namespace blank { + +class Connection; +class World; + +class Server { + +public: + struct Config { + Uint16 port = 12354; + }; + +public: + Server(const Config &, World &); + ~Server(); + + void Handle(); + + void Update(int dt); + +private: + void HandlePacket(const UDPpacket &); + + Connection &GetClient(const IPaddress &); + + void OnConnect(Connection &); + void OnDisconnect(Connection &); + +private: + UDPsocket serv_sock; + UDPpacket serv_pack; + std::list clients; + + World &world; + +}; + +} + +#endif diff --git a/src/net/io.hpp b/src/net/io.hpp new file mode 100644 index 0000000..d3fbba2 --- /dev/null +++ b/src/net/io.hpp @@ -0,0 +1,13 @@ +#ifndef BLANK_NET_IO_HPP +#define BLANK_NET_IO_HPP + +#include + + +namespace blank { + +std::ostream &operator <<(std::ostream &, const IPaddress &); + +} + +#endif diff --git a/src/net/net.cpp b/src/net/net.cpp new file mode 100644 index 0000000..73ed73c --- /dev/null +++ b/src/net/net.cpp @@ -0,0 +1,247 @@ +#include "Client.hpp" +#include "Connection.hpp" +#include "io.hpp" +#include "Packet.hpp" +#include "Server.hpp" + +#include "../app/init.hpp" + +#include +#include + +using namespace std; + + +namespace blank { + +namespace { + +UDPsocket client_bind(Uint16 port) { + UDPsocket sock = SDLNet_UDP_Open(port); + if (!sock) { + throw NetError("SDLNet_UDP_Open"); + } + return sock; +} + +IPaddress client_resolve(const char *host, Uint16 port) { + IPaddress addr; + if (SDLNet_ResolveHost(&addr, host, port) != 0) { + throw NetError("SDLNet_ResolveHost"); + } + return addr; +} + +} + +Client::Client(const Config &conf, World &world) +: world(world) +, conn(client_resolve(conf.host.c_str(), conf.port)) +, client_sock(client_bind(0)) +, client_pack{ -1, nullptr, 0 } { + client_pack.data = new Uint8[sizeof(Packet)]; + client_pack.maxlen = sizeof(Packet); + // establish connection + conn.SendPing(client_pack, client_sock); +} + +Client::~Client() { + delete[] client_pack.data; + SDLNet_UDP_Close(client_sock); +} + + +void Client::Handle() { + int result = SDLNet_UDP_Recv(client_sock, &client_pack); + while (result > 0) { + HandlePacket(client_pack); + result = SDLNet_UDP_Recv(client_sock, &client_pack); + } + if (result == -1) { + // a boo boo happened + throw NetError("SDLNet_UDP_Recv"); + } +} + +void Client::HandlePacket(const UDPpacket &udp_pack) { + if (!conn.Matches(udp_pack.address)) { + // packet came from somewhere else, drop + return; + } + const Packet &pack = *reinterpret_cast(udp_pack.data); + if (pack.header.tag != Packet::TAG) { + // mistagged packet, drop + return; + } + + conn.FlagRecv(); + cout << "I got something!" << endl; +} + +void Client::Update(int dt) { + conn.Update(dt); + if (conn.TimedOut()) { + cout << "connection timed out :(" << endl; + } else if (conn.ShouldPing()) { + conn.SendPing(client_pack, client_sock); + } +} + + +Connection::Connection(const IPaddress &addr) +: addr(addr) +, send_timer(5000) +, recv_timer(10000) { + send_timer.Start(); + recv_timer.Start(); +} + +bool Connection::Matches(const IPaddress &remote) const noexcept { + return memcmp(&addr, &remote, sizeof(IPaddress)) == 0; +} + +void Connection::FlagSend() noexcept { + send_timer.Reset(); +} + +void Connection::FlagRecv() noexcept { + recv_timer.Reset(); +} + +bool Connection::ShouldPing() const noexcept { + return send_timer.HitOnce(); +} + +bool Connection::TimedOut() const noexcept { + return recv_timer.HitOnce(); +} + +void Connection::Update(int dt) { + send_timer.Update(dt); + recv_timer.Update(dt); +} + + +void Connection::Send(UDPpacket &pack, UDPsocket sock) { + pack.address = addr; + if (SDLNet_UDP_Send(sock, -1, &pack) == 0) { + throw NetError("SDLNet_UDP_Send"); + } + FlagSend(); +} + +void Connection::SendPing(UDPpacket &udp_pack, UDPsocket sock) { + Packet &pack = *reinterpret_cast(udp_pack.data); + udp_pack.len = pack.Ping(); + Send(udp_pack, sock); +} + + +ostream &operator <<(ostream &out, const IPaddress &addr) { + const unsigned char *host = reinterpret_cast(&addr.host); + out << int(host[0]) + << '.' << int(host[1]) + << '.' << int(host[2]) + << '.' << int(host[3]); + if (addr.port) { + out << ':' << SDLNet_Read16(&addr.port); + } + return out; +} + + +void Packet::Tag() noexcept { + header.tag = TAG; +} + +size_t Packet::Ping() noexcept { + Tag(); + header.type = PING; + return sizeof(Header); +} + + +Server::Server(const Config &conf, World &world) +: serv_sock(nullptr) +, serv_pack{ -1, nullptr, 0 } +, clients() +, world(world) { + serv_sock = SDLNet_UDP_Open(conf.port); + if (!serv_sock) { + throw NetError("SDLNet_UDP_Open"); + } + + serv_pack.data = new Uint8[sizeof(Packet)]; + serv_pack.maxlen = sizeof(Packet); +} + +Server::~Server() { + delete[] serv_pack.data; + SDLNet_UDP_Close(serv_sock); +} + + +void Server::Handle() { + int result = SDLNet_UDP_Recv(serv_sock, &serv_pack); + while (result > 0) { + HandlePacket(serv_pack); + result = SDLNet_UDP_Recv(serv_sock, &serv_pack); + } + if (result == -1) { + // a boo boo happened + throw NetError("SDLNet_UDP_Recv"); + } +} + +void Server::HandlePacket(const UDPpacket &udp_pack) { + if (udp_pack.len < int(sizeof(Packet::Header))) { + // packet too small, drop + return; + } + const Packet &pack = *reinterpret_cast(udp_pack.data); + if (pack.header.tag != Packet::TAG) { + // mistagged packet, drop + return; + } + + Connection &client = GetClient(udp_pack.address); + client.FlagRecv(); +} + +Connection &Server::GetClient(const IPaddress &addr) { + for (Connection &client : clients) { + if (client.Matches(addr)) { + return client; + } + } + clients.emplace_back(addr); + OnConnect(clients.back()); + return clients.back(); +} + +void Server::OnConnect(Connection &client) { + cout << "new connection from " << client.Address() << endl; + // tell it we're alive + client.SendPing(serv_pack, serv_sock); +} + +void Server::Update(int dt) { + for (list::iterator client(clients.begin()), end(clients.end()); client != end;) { + client->Update(dt); + if (client->TimedOut()) { + OnDisconnect(*client); + client = clients.erase(client); + } else { + if (client->ShouldPing()) { + client->SendPing(serv_pack, serv_sock); + } + ++client; + } + } +} + +void Server::OnDisconnect(Connection &client) { + cout << "connection timeout from " << client.Address() << endl; +} + +} diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index acf877f..e637661 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -125,8 +125,8 @@ Interface::Interface( , remove_timer(256) , remove(0) , selection(0) -, place_sound(env.assets.LoadSound("thump")) -, remove_sound(env.assets.LoadSound("plop")) +, place_sound(env.loader.LoadSound("thump")) +, remove_sound(env.loader.LoadSound("plop")) , fwd(0) , rev(0) , debug(false) { diff --git a/src/world/ChunkRenderer.hpp b/src/world/ChunkRenderer.hpp index 3240c3d..fe4f6a6 100644 --- a/src/world/ChunkRenderer.hpp +++ b/src/world/ChunkRenderer.hpp @@ -11,7 +11,7 @@ namespace blank { -class Assets; +class AssetLoader; class TextureIndex; class Viewport; class World; @@ -22,7 +22,7 @@ public: /// render_distance in chunks, excluding the base chunk which is always rendered ChunkRenderer(World &, int render_distance); - void LoadTextures(const Assets &, const TextureIndex &); + void LoadTextures(const AssetLoader &, const TextureIndex &); void FogDensity(float d) noexcept { fog_density = d; } bool InRange(const Chunk::Pos &) const noexcept; diff --git a/src/world/render.cpp b/src/world/render.cpp index f701faf..4a237e7 100644 --- a/src/world/render.cpp +++ b/src/world/render.cpp @@ -24,9 +24,9 @@ ChunkRenderer::ChunkRenderer(World &world, int rd) } -void ChunkRenderer::LoadTextures(const Assets &assets, const TextureIndex &tex_index) { +void ChunkRenderer::LoadTextures(const AssetLoader &loader, const TextureIndex &tex_index) { block_tex.Bind(); - assets.LoadTextures(tex_index, block_tex); + loader.LoadTextures(tex_index, block_tex); block_tex.FilterNearest(); } -- 2.39.2 From dbfcb12348b80e2582f710acb1e4ed0011889ba2 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Wed, 2 Sep 2015 21:18:49 +0200 Subject: [PATCH 04/16] special treatment for players with some drawbacks and cut corners, but more to come --- running | 9 +-- src/ai/Spawner.cpp | 31 +++++++--- src/ai/Spawner.hpp | 3 +- src/app/WorldState.cpp | 10 ++-- src/app/runtime.cpp | 8 +++ src/ui/Interface.hpp | 11 ++-- src/ui/Keymap.hpp | 5 -- src/ui/ui.cpp | 125 +---------------------------------------- src/world/World.cpp | 37 +++++++----- src/world/World.hpp | 11 +++- 10 files changed, 80 insertions(+), 170 deletions(-) diff --git a/running b/running index 7c089ff..846f27d 100644 --- a/running +++ b/running @@ -69,6 +69,11 @@ Network --port port number to connection to (client) or listen on (server) +--player-name + use given name to identify with the server (client mode) + default player name is "default" + the server will reject players with names that are already taken + World ----- @@ -97,10 +102,6 @@ flip through available blocks. Q changes the face of the active block (loops over up, down, right, left, front, and back) and E changes the turn (none, left, around, and right). -Pressing B prints details about the block you're pointing at and P prints -info about the active block. L spits out the player position and light -level there. C dumps info about the chunk of the pointed at block. - Press N to toggle player/world collision. F1 toggles UI rendering. diff --git a/src/ai/Spawner.cpp b/src/ai/Spawner.cpp index fb2a1ea..6a692b1 100644 --- a/src/ai/Spawner.cpp +++ b/src/ai/Spawner.cpp @@ -52,7 +52,6 @@ Spawner::Spawner(World &world, std::uint64_t seed) } timer.Start(); - Spawn(world.Player().ChunkCoords(), { 0.5f, 0.5f, 0.5f }); } Spawner::~Spawner() { @@ -75,7 +74,7 @@ void Spawner::Update(int dt) { void Spawner::CheckDespawn() noexcept { - const Entity &reference = world.Player(); + const auto &refs = world.Players(); for (auto iter = controllers.begin(), end = controllers.end(); iter != end;) { Entity &e = (*iter)->Controlled(); if (e.Dead()) { @@ -83,8 +82,15 @@ void Spawner::CheckDespawn() noexcept { iter = controllers.erase(iter); continue; } - glm::vec3 diff(reference.AbsoluteDifference(e)); - if (dot(diff, diff) > despawn_range) { + bool safe = false; + for (const Entity *ref : refs) { + glm::vec3 diff(ref->AbsoluteDifference(e)); + if (dot(diff, diff) < despawn_range) { + safe = true; + break; + } + } + if (!safe) { e.Kill(); delete *iter; iter = controllers.erase(iter); @@ -97,6 +103,11 @@ void Spawner::CheckDespawn() noexcept { void Spawner::TrySpawn() { if (controllers.size() >= max_entities) return; + // select random player to punish + auto &players = world.Players(); + if (players.size() == 0) return; + Entity &player = *players[random.Next() % players.size()]; + glm::ivec3 chunk( (random.Next() % (chunk_range * 2 + 1)) - chunk_range, (random.Next() % (chunk_range * 2 + 1)) - chunk_range, @@ -111,14 +122,16 @@ void Spawner::TrySpawn() { // distance check - glm::vec3 diff(glm::vec3(chunk * Chunk::Extent() - pos) + world.Player().Position()); + glm::vec3 diff(glm::vec3(chunk * Chunk::Extent() - pos) + player.Position()); float dist = dot(diff, diff); if (dist > despawn_range || dist < spawn_distance) { return; } // check if the spawn block and the one above it are loaded and inhabitable - BlockLookup spawn_block(&world.PlayerChunk(), chunk * Chunk::Extent() + pos); + BlockLookup spawn_block( + world.Loader().Loaded(player.ChunkCoords()), + chunk * Chunk::Extent() + pos); if (!spawn_block || spawn_block.GetType().collide_block) { return; } @@ -128,10 +141,10 @@ void Spawner::TrySpawn() { return; } - Spawn(world.Player().ChunkCoords() + chunk, glm::vec3(pos) + glm::vec3(0.5f)); + Spawn(player, player.ChunkCoords() + chunk, glm::vec3(pos) + glm::vec3(0.5f)); } -void Spawner::Spawn(const glm::ivec3 &chunk, const glm::vec3 &pos) { +void Spawner::Spawn(Entity &reference, const glm::ivec3 &chunk, const glm::vec3 &pos) { glm::vec3 rot(0.000001f); rot.x *= (random.Next() % 1024); rot.y *= (random.Next() % 1024); @@ -148,7 +161,7 @@ void Spawner::Spawn(const glm::ivec3 &chunk, const glm::vec3 &pos) { if (random()) { ctrl = new RandomWalk(e, random.Next()); } else { - ctrl = new Chaser(world, e, world.Player()); + ctrl = new Chaser(world, e, reference); } controllers.emplace_back(ctrl); } diff --git a/src/ai/Spawner.hpp b/src/ai/Spawner.hpp index 9fb3d36..a1181d3 100644 --- a/src/ai/Spawner.hpp +++ b/src/ai/Spawner.hpp @@ -13,6 +13,7 @@ namespace blank { class Controller; +class Entity; class World; class Spawner { @@ -26,7 +27,7 @@ public: private: void CheckDespawn() noexcept; void TrySpawn(); - void Spawn(const glm::ivec3 &, const glm::vec3 &); + void Spawn(Entity &reference, const glm::ivec3 &, const glm::vec3 &); private: World &world; diff --git a/src/app/WorldState.cpp b/src/app/WorldState.cpp index 03abe36..521f5b5 100644 --- a/src/app/WorldState.cpp +++ b/src/app/WorldState.cpp @@ -70,20 +70,20 @@ void WorldState::Update(int dt) { interface.Update(dt); spawner.Update(dt); world.Update(dt); - chunk_renderer.Rebase(world.Player().ChunkCoords()); + chunk_renderer.Rebase(interface.Player().ChunkCoords()); chunk_renderer.Update(dt); - glm::mat4 trans = world.Player().Transform(world.Player().ChunkCoords()); + glm::mat4 trans = interface.Player().Transform(interface.Player().ChunkCoords()); glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f)); glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)); - env.audio.Position(world.Player().Position()); - env.audio.Velocity(world.Player().Velocity()); + env.audio.Position(interface.Player().Position()); + env.audio.Velocity(interface.Player().Velocity()); env.audio.Orientation(dir, up); } void WorldState::Render(Viewport &viewport) { - viewport.WorldPosition(world.Player().Transform(world.Player().ChunkCoords())); + viewport.WorldPosition(interface.Player().Transform(interface.Player().ChunkCoords())); chunk_renderer.Render(viewport); world.Render(viewport); interface.Render(viewport); diff --git a/src/app/runtime.cpp b/src/app/runtime.cpp index 2f0f82e..f979c65 100644 --- a/src/app/runtime.cpp +++ b/src/app/runtime.cpp @@ -142,6 +142,14 @@ void Runtime::ReadArgs(int argc, const char *const *argv) { config.server.port = strtoul(argv[i], nullptr, 10); config.client.port = config.server.port; } + } else if (strcmp(param, "player-name") == 0) { + ++i; + if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') { + cerr << "missing argument to --player-name" << endl; + error = true; + } else { + config.interface.player_name = argv[i]; + } } else if (strcmp(param, "save-path") == 0) { ++i; if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') { diff --git a/src/ui/Interface.hpp b/src/ui/Interface.hpp index 5cf8207..aab8b15 100644 --- a/src/ui/Interface.hpp +++ b/src/ui/Interface.hpp @@ -28,6 +28,8 @@ class Interface { public: struct Config { + std::string player_name = "default"; + float move_velocity = 0.005f; float pitch_sensitivity = -0.0025f; float yaw_sensitivity = -0.001f; @@ -40,6 +42,9 @@ public: Interface(const Config &, Environment &, World &); + Entity &Player() noexcept { return ctrl.Controlled(); } + const Entity &Player() const noexcept { return ctrl.Controlled(); } + void HandlePress(const SDL_KeyboardEvent &); void HandleRelease(const SDL_KeyboardEvent &); void Handle(const SDL_MouseMotionEvent &); @@ -56,12 +61,6 @@ public: void PlaceBlock(); void RemoveBlock() noexcept; - void PrintBlockInfo(); - void PrintChunkInfo(); - void PrintLightInfo(); - void PrintSelectionInfo(); - void Print(const Block &); - void SelectNext(); void SelectPrevious(); diff --git a/src/ui/Keymap.hpp b/src/ui/Keymap.hpp index e96179a..0e34ba9 100644 --- a/src/ui/Keymap.hpp +++ b/src/ui/Keymap.hpp @@ -35,11 +35,6 @@ public: TOGGLE_VISUAL, TOGGLE_DEBUG, - PRINT_BLOCK, - PRINT_CHUNK, - PRINT_LIGHT, - PRINT_SELECTION, - EXIT, }; diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index e637661..d8459e5 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -105,7 +105,8 @@ Interface::Interface( World &world) : env(env) , world(world) -, ctrl(world.Player()) +// let's assume this succeeds and hope for the best for now +, ctrl(*world.AddPlayer(config.player_name)) , hud(world.BlockTypes(), env.assets.small_ui_font) , aim{{ 0, 0, 0 }, { 0, 0, -1 }} , aim_world() @@ -204,19 +205,6 @@ void Interface::HandlePress(const SDL_KeyboardEvent &event) { ToggleCollision(); break; - case Keymap::PRINT_BLOCK: - PrintBlockInfo(); - break; - case Keymap::PRINT_CHUNK: - PrintChunkInfo(); - break; - case Keymap::PRINT_LIGHT: - PrintLightInfo(); - break; - case Keymap::PRINT_SELECTION: - PrintSelectionInfo(); - break; - case Keymap::TOGGLE_VISUAL: ToggleVisual(); break; @@ -283,92 +271,6 @@ void Interface::ToggleCollision() { } } -void Interface::PrintBlockInfo() { - std::cout << std::endl; - if (!aim_world) { - PostMessage("not looking at any block"); - Ray aim = ctrl.Aim(); - std::stringstream s; - s << "aim ray: " << aim.orig << ", " << aim.dir; - PostMessage(s.str()); - return; - } - std::stringstream s; - s << "looking at block " << aim_world.block - << " " << aim_world.BlockCoords() - << " of chunk " << aim_world.GetChunk().Position() - ; - PostMessage(s.str()); - Print(aim_world.GetBlock()); -} - -void Interface::PrintChunkInfo() { - std::cout << std::endl; - if (!aim_world) { - PostMessage("not looking at any block"); - return; - } - std::stringstream s; - s << "looking at chunk " << aim_world.GetChunk().Position(); - PostMessage(s.str()); - - PostMessage(" neighbors:"); - if (aim_world.GetChunk().HasNeighbor(Block::FACE_LEFT)) { - s.str(""); - s << " left " << aim_world.GetChunk().GetNeighbor(Block::FACE_LEFT).Position(); - PostMessage(s.str()); - } - if (aim_world.GetChunk().HasNeighbor(Block::FACE_RIGHT)) { - s.str(""); - s << " right " << aim_world.GetChunk().GetNeighbor(Block::FACE_RIGHT).Position(); - PostMessage(s.str()); - } - if (aim_world.GetChunk().HasNeighbor(Block::FACE_UP)) { - s.str(""); - s << " up " << aim_world.GetChunk().GetNeighbor(Block::FACE_UP).Position(); - PostMessage(s.str()); - } - if (aim_world.GetChunk().HasNeighbor(Block::FACE_DOWN)) { - s.str(""); - s << " down " << aim_world.GetChunk().GetNeighbor(Block::FACE_DOWN).Position(); - PostMessage(s.str()); - } - if (aim_world.GetChunk().HasNeighbor(Block::FACE_FRONT)) { - s.str(""); - s << " front " << aim_world.GetChunk().GetNeighbor(Block::FACE_FRONT).Position(); - PostMessage(s.str()); - } - if (aim_world.GetChunk().HasNeighbor(Block::FACE_BACK)) { - s.str(""); - s << " back " << aim_world.GetChunk().GetNeighbor(Block::FACE_BACK).Position(); - PostMessage(s.str()); - } - std::cout << std::endl; -} - -void Interface::PrintLightInfo() { - std::stringstream s; - s - << "light level " << world.PlayerChunk().GetLight(world.Player().Position()) - << " at position " << world.Player().Position() - ; - PostMessage(s.str()); -} - -void Interface::PrintSelectionInfo() { - std::cout << std::endl; - Print(selection); -} - -void Interface::Print(const Block &block) { - std::stringstream s; - s << "type: " << block.type - << ", face: " << block.GetFace() - << ", turn: " << block.GetTurn() - ; - PostMessage(s.str()); -} - void Interface::ToggleAudio() { config.audio_disabled = !config.audio_disabled; if (config.audio_disabled) { @@ -623,7 +525,7 @@ void Interface::UpdateOutline() { outl_buf.Clear(); aim_world.GetType().FillOutlineModel(outl_buf); outline.Update(outl_buf); - outline_transform = aim_world.GetChunk().Transform(world.Player().ChunkCoords()); + outline_transform = aim_world.GetChunk().Transform(Player().ChunkCoords()); outline_transform *= aim_world.BlockTransform(); outline_transform *= glm::scale(glm::vec3(1.005f)); } @@ -710,11 +612,6 @@ void Keymap::LoadDefault() { Map(SDL_SCANCODE_F3, TOGGLE_DEBUG); Map(SDL_SCANCODE_F4, TOGGLE_AUDIO); - Map(SDL_SCANCODE_B, PRINT_BLOCK); - Map(SDL_SCANCODE_C, PRINT_CHUNK); - Map(SDL_SCANCODE_L, PRINT_LIGHT); - Map(SDL_SCANCODE_P, PRINT_SELECTION); - Map(SDL_SCANCODE_ESCAPE, EXIT); } @@ -806,14 +703,6 @@ const char *Keymap::ActionToString(Action action) { return "toggle_visual"; case TOGGLE_DEBUG: return "toggle_debug"; - case PRINT_BLOCK: - return "print_block"; - case PRINT_CHUNK: - return "print_chunk"; - case PRINT_LIGHT: - return "print_light"; - case PRINT_SELECTION: - return "print_selection"; case EXIT: return "exit"; } @@ -854,14 +743,6 @@ Keymap::Action Keymap::StringToAction(const std::string &str) { return TOGGLE_VISUAL; } else if (str == "toggle_debug") { return TOGGLE_DEBUG; - } else if (str == "print_block") { - return PRINT_BLOCK; - } else if (str == "print_chunk") { - return PRINT_CHUNK; - } else if (str == "print_light") { - return PRINT_LIGHT; - } else if (str == "print_selection") { - return PRINT_SELECTION; } else if (str == "exit") { return EXIT; } else { diff --git a/src/world/World.cpp b/src/world/World.cpp index 1d25e01..06ccb62 100644 --- a/src/world/World.cpp +++ b/src/world/World.cpp @@ -15,24 +15,35 @@ namespace blank { World::World(const BlockTypeRegistry &types, const Config &config, const WorldSave &save) -: block_type(types) +: config(config) +, block_type(types) , generate(config.gen) , chunks(config.load, types, generate, save) -, player() +, players() , entities() , light_direction(config.light_direction) , fog_density(config.fog_density) { generate.Space(0); generate.Light(13); generate.Solids({ 1, 4, 7, 10 }); +} - player = &AddEntity(); - player->Name("player"); - player->Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } }); - player->WorldCollidable(true); - player->Position(config.spawn); - chunks.QueueSurrounding(player->ChunkCoords()); +Entity *World::AddPlayer(const std::string &name) { + for (Entity *e : players) { + if (e->Name() == name) { + return nullptr; + } + } + Entity &player = AddEntity(); + player.Name(name); + // TODO: load from save file here + player.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } }); + player.WorldCollidable(true); + player.Position(config.spawn); + players.push_back(&player); + chunks.QueueSurrounding(player.ChunkCoords()); + return &player; } @@ -133,11 +144,6 @@ bool World::Intersection(const Entity &e, std::vector &col) { } -Chunk &World::PlayerChunk() { - return chunks.ForceLoad(player->ChunkCoords()); -} - - namespace { std::vector col; @@ -162,7 +168,8 @@ void World::Update(int dt) { ++iter; } } - chunks.Rebase(player->ChunkCoords()); + // TODO: make flexible + chunks.Rebase(players[0]->ChunkCoords()); chunks.Update(dt); } @@ -205,7 +212,7 @@ void World::Render(Viewport &viewport) { entity_prog.SetFogDensity(fog_density); for (Entity &entity : entities) { - entity.Render(entity.ChunkTransform(player->ChunkCoords()), entity_prog); + entity.Render(entity.ChunkTransform(players[0]->ChunkCoords()), entity_prog); } } diff --git a/src/world/World.hpp b/src/world/World.hpp index 76cdeb2..3981b28 100644 --- a/src/world/World.hpp +++ b/src/world/World.hpp @@ -63,22 +63,27 @@ public: const BlockTypeRegistry &BlockTypes() noexcept { return block_type; } ChunkLoader &Loader() noexcept { return chunks; } - Entity &Player() { return *player; } + /// add player with given name + /// returns nullptr if the name is already taken + Entity *AddPlayer(const std::string &name); Entity &AddEntity() { entities.emplace_back(); return entities.back(); } - Chunk &PlayerChunk(); + const std::vector &Players() const noexcept { return players; } + const std::list &Entities() const noexcept { return entities; } void Update(int dt); void Render(Viewport &); private: + Config config; + const BlockTypeRegistry &block_type; Generator generate; ChunkLoader chunks; - Entity *player; + std::vector players; std::list entities; glm::vec3 light_direction; -- 2.39.2 From 104592aabdc70b21065c35fe4d092fc6cdaa1f49 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Wed, 2 Sep 2015 22:33:08 +0200 Subject: [PATCH 05/16] client-side implementation of login packet chunk loading has to change: need to stop the client from generating chunks on its own and the server must be able to load chunks for multiple bases also, I've not written a single test for all this crap shame on me --- .gitignore | 1 + Makefile | 5 +++- building => doc/building | 4 +++ doc/protocol | 25 ++++++++++++++++ running => doc/running | 0 src/app/ClientState.cpp | 58 +++++++++++++++++++++++++++++++++++-- src/app/ClientState.hpp | 7 +++++ src/app/WorldState.cpp | 1 - src/app/runtime.cpp | 2 +- src/net/Client.hpp | 3 ++ src/net/Packet.hpp | 5 +++- src/net/Server.hpp | 2 ++ src/net/net.cpp | 62 ++++++++++++++++++++++++++++++++++++++-- 13 files changed, 166 insertions(+), 9 deletions(-) rename building => doc/building (90%) create mode 100644 doc/protocol rename running => doc/running (100%) diff --git a/.gitignore b/.gitignore index eeb5465..5a84607 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ blank.test build cachegrind.out.* callgrind.out.* +client-saves saves diff --git a/Makefile b/Makefile index e6e24d9..c23e9ac 100644 --- a/Makefile +++ b/Makefile @@ -67,6 +67,9 @@ run: $(ASSET_DEP) blank server: $(ASSET_DEP) blank ./blank --server --save-path saves/ +client: $(ASSET_DEP) blank + ./blank --client --save-path client-saves/ + gdb: $(ASSET_DEP) blank.debug gdb ./blank.debug @@ -88,7 +91,7 @@ clean: distclean: clean rm -f $(BIN) cachegrind.out.* callgrind.out.* - rm -Rf build saves + rm -Rf build client-saves saves .PHONY: all release debug profile tests run gdb cachegrind callgrind test clean distclean diff --git a/building b/doc/building similarity index 90% rename from building rename to doc/building index ef83775..b850668 100644 --- a/building +++ b/doc/building @@ -34,6 +34,10 @@ run: server: same as run, only in server mode +server: + same as run, only in client mode and the save path is set to + ./client-saved to prevent clashes with a running `make server` + test: build and run unittests diff --git a/doc/protocol b/doc/protocol new file mode 100644 index 0000000..723f3cf --- /dev/null +++ b/doc/protocol @@ -0,0 +1,25 @@ +Packets +======= + +Ping +---- + +To tell the other side we're still alive. +Both server and client will send this if they haven't sent something in +a while. + +Code: 0 +Payload: none + + +Login +----- + +Sent from client to serveri as a request to join. The server may +respond negatively if the player name is already taken or some cap has +been reached. + +Code: 1 +Payload: + player name, max 32 byte UTF-8 string, + shorter names should be zero terminated diff --git a/running b/doc/running similarity index 100% rename from running rename to doc/running diff --git a/src/app/ClientState.cpp b/src/app/ClientState.cpp index c371235..b4ed3f8 100644 --- a/src/app/ClientState.cpp +++ b/src/app/ClientState.cpp @@ -1,6 +1,7 @@ #include "ClientState.hpp" #include "Environment.hpp" +#include "init.hpp" #include "TextureIndex.hpp" namespace blank { @@ -9,19 +10,55 @@ ClientState::ClientState( Environment &env, const World::Config &wc, const WorldSave &ws, + const Interface::Config &ic, const Client::Config &cc ) : env(env) , block_types() , world(block_types, wc, ws) +, chunk_renderer(world, wc.load.load_dist) +, interface(ic, env, world) , client(cc, world) { + TextureIndex tex_index; + env.loader.LoadBlockTypes("default", block_types, tex_index); + chunk_renderer.LoadTextures(env.loader, tex_index); + chunk_renderer.FogDensity(wc.fog_density); + // TODO: better solution for initializing HUD + interface.SelectNext(); + client.SendLogin(ic.player_name); +} + +void ClientState::OnEnter() { + env.window.GrabMouse(); } void ClientState::Handle(const SDL_Event &event) { - if (event.type == SDL_QUIT) { - env.state.PopAll(); + switch (event.type) { + case SDL_KEYDOWN: + interface.HandlePress(event.key); + break; + case SDL_KEYUP: + interface.HandleRelease(event.key); + break; + case SDL_MOUSEBUTTONDOWN: + interface.HandlePress(event.button); + break; + case SDL_MOUSEBUTTONUP: + interface.HandleRelease(event.button); + break; + case SDL_MOUSEMOTION: + interface.Handle(event.motion); + break; + case SDL_MOUSEWHEEL: + interface.Handle(event.wheel); + break; + case SDL_QUIT: + env.state.Pop(); + break; + default: + break; } } @@ -32,11 +69,26 @@ void ClientState::Update(int dt) { if (client.TimedOut()) { env.state.Pop(); } + + interface.Update(dt); + world.Update(dt); + chunk_renderer.Rebase(interface.Player().ChunkCoords()); + chunk_renderer.Update(dt); + + glm::mat4 trans = interface.Player().Transform(interface.Player().ChunkCoords()); + glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f)); + glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)); + env.audio.Position(interface.Player().Position()); + env.audio.Velocity(interface.Player().Velocity()); + env.audio.Orientation(dir, up); } void ClientState::Render(Viewport &viewport) { - + viewport.WorldPosition(interface.Player().Transform(interface.Player().ChunkCoords())); + chunk_renderer.Render(viewport); + world.Render(viewport); + interface.Render(viewport); } } diff --git a/src/app/ClientState.hpp b/src/app/ClientState.hpp index f54e56f..8868c98 100644 --- a/src/app/ClientState.hpp +++ b/src/app/ClientState.hpp @@ -3,7 +3,9 @@ #include "State.hpp" #include "../net/Client.hpp" +#include "../ui/Interface.hpp" #include "../world/BlockTypeRegistry.hpp" +#include "../world/ChunkRenderer.hpp" #include "../world/World.hpp" @@ -19,9 +21,12 @@ public: Environment &, const World::Config &, const WorldSave &, + const Interface::Config &, const Client::Config & ); + void OnEnter() override; + void Handle(const SDL_Event &) override; void Update(int dt) override; void Render(Viewport &) override; @@ -30,6 +35,8 @@ private: Environment &env; BlockTypeRegistry block_types; World world; + ChunkRenderer chunk_renderer; + Interface interface; Client client; }; diff --git a/src/app/WorldState.cpp b/src/app/WorldState.cpp index 521f5b5..fabd332 100644 --- a/src/app/WorldState.cpp +++ b/src/app/WorldState.cpp @@ -79,7 +79,6 @@ void WorldState::Update(int dt) { env.audio.Position(interface.Player().Position()); env.audio.Velocity(interface.Player().Velocity()); env.audio.Orientation(dir, up); - } void WorldState::Render(Viewport &viewport) { diff --git a/src/app/runtime.cpp b/src/app/runtime.cpp index f979c65..2d12863 100644 --- a/src/app/runtime.cpp +++ b/src/app/runtime.cpp @@ -357,7 +357,7 @@ void Runtime::RunClient() { } Application app(env); - ClientState client_state(env, config.world, save, config.client); + ClientState client_state(env, config.world, save, config.interface, config.client); app.PushState(&client_state); Run(app); } diff --git a/src/net/Client.hpp b/src/net/Client.hpp index 63c3fdd..5b83061 100644 --- a/src/net/Client.hpp +++ b/src/net/Client.hpp @@ -29,6 +29,9 @@ public: bool TimedOut() { return conn.TimedOut(); } + void SendPing(); + void SendLogin(const std::string &); + private: void HandlePacket(const UDPpacket &); diff --git a/src/net/Packet.hpp b/src/net/Packet.hpp index 7b482e5..d547990 100644 --- a/src/net/Packet.hpp +++ b/src/net/Packet.hpp @@ -2,6 +2,7 @@ #define BLANK_NET_PACKET_HPP_ #include +#include namespace blank { @@ -11,7 +12,8 @@ struct Packet { static constexpr std::uint32_t TAG = 0xFB1AB1AF; enum Type { - PING, + PING = 0, + LOGIN = 1, }; struct Header { @@ -25,6 +27,7 @@ struct Packet { void Tag() noexcept; std::size_t Ping() noexcept; + std::size_t Login(const std::string &name) noexcept; }; diff --git a/src/net/Server.hpp b/src/net/Server.hpp index b6a72a9..6a1e996 100644 --- a/src/net/Server.hpp +++ b/src/net/Server.hpp @@ -33,6 +33,8 @@ private: void OnConnect(Connection &); void OnDisconnect(Connection &); + void HandleLogin(Connection &client, const UDPpacket &); + private: UDPsocket serv_sock; UDPpacket serv_pack; diff --git a/src/net/net.cpp b/src/net/net.cpp index 73ed73c..20b6f5c 100644 --- a/src/net/net.cpp +++ b/src/net/net.cpp @@ -5,6 +5,7 @@ #include "Server.hpp" #include "../app/init.hpp" +#include "../world/World.hpp" #include #include @@ -42,7 +43,7 @@ Client::Client(const Config &conf, World &world) client_pack.data = new Uint8[sizeof(Packet)]; client_pack.maxlen = sizeof(Packet); // establish connection - conn.SendPing(client_pack, client_sock); + SendPing(); } Client::~Client() { @@ -83,10 +84,20 @@ void Client::Update(int dt) { if (conn.TimedOut()) { cout << "connection timed out :(" << endl; } else if (conn.ShouldPing()) { - conn.SendPing(client_pack, client_sock); + SendPing(); } } +void Client::SendPing() { + conn.SendPing(client_pack, client_sock); +} + +void Client::SendLogin(const string &name) { + Packet &pack = *reinterpret_cast(client_pack.data); + client_pack.len = pack.Login(name); + conn.Send(client_pack, client_sock); +} + Connection::Connection(const IPaddress &addr) : addr(addr) @@ -160,6 +171,20 @@ size_t Packet::Ping() noexcept { return sizeof(Header); } +size_t Packet::Login(const string &name) noexcept { + constexpr size_t maxname = 32; + + Tag(); + header.type = LOGIN; + if (name.size() < maxname) { + memset(payload, '\0', maxname); + memcpy(payload, name.c_str(), name.size()); + } else { + memcpy(payload, name.c_str(), maxname); + } + return sizeof(Header) + maxname; +} + Server::Server(const Config &conf, World &world) : serv_sock(nullptr) @@ -206,6 +231,18 @@ void Server::HandlePacket(const UDPpacket &udp_pack) { Connection &client = GetClient(udp_pack.address); client.FlagRecv(); + + switch (pack.header.type) { + case Packet::PING: + // already done all that's supposed to do + break; + case Packet::LOGIN: + HandleLogin(client, udp_pack); + break; + default: + // just drop packets of unknown type + break; + } } Connection &Server::GetClient(const IPaddress &addr) { @@ -244,4 +281,25 @@ void Server::OnDisconnect(Connection &client) { cout << "connection timeout from " << client.Address() << endl; } + +void Server::HandleLogin(Connection &client, const UDPpacket &udp_pack) { + const Packet &pack = *reinterpret_cast(udp_pack.data); + size_t maxlen = min(udp_pack.len - int(sizeof(Packet::Header)), 32); + string name; + name.reserve(maxlen); + for (size_t i = 0; i < maxlen && pack.payload[i] != '\0'; ++i) { + name.push_back(pack.payload[i]); + } + cout << "got login request from player \"" << name << '"' << endl; + + Entity *player = world.AddPlayer(name); + if (player) { + // success! + cout << "\taccepted" << endl; + } else { + // aw no :( + cout << "\trejected" << endl; + } +} + } -- 2.39.2 From 09b734344f31e18d0fa31c39acba6d012aa2cc56 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Wed, 2 Sep 2015 23:40:21 +0200 Subject: [PATCH 06/16] tag packets withsequence numbers acks are already transmitted to the other side, but they're not used yet --- src/net/Connection.hpp | 13 +++++++---- src/net/Packet.hpp | 7 ++++++ src/net/net.cpp | 52 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/net/Connection.hpp b/src/net/Connection.hpp index 49e9cda..ade65eb 100644 --- a/src/net/Connection.hpp +++ b/src/net/Connection.hpp @@ -1,8 +1,10 @@ #ifndef BLANK_NET_CONNECTION_HPP_ #define BLANK_NET_CONNECTION_HPP_ +#include "Packet.hpp" #include "../app/IntervalTimer.hpp" +#include #include @@ -17,24 +19,27 @@ public: bool Matches(const IPaddress &) const noexcept; - void FlagSend() noexcept; - void FlagRecv() noexcept; - bool ShouldPing() const noexcept; bool TimedOut() const noexcept; void Update(int dt); - void SendPing(UDPpacket &, UDPsocket); void Send(UDPpacket &, UDPsocket); + void Received(const UDPpacket &); + +private: + void FlagSend() noexcept; + void FlagRecv() noexcept; private: IPaddress addr; IntervalTimer send_timer; IntervalTimer recv_timer; + Packet::TControl ctrl; + }; } diff --git a/src/net/Packet.hpp b/src/net/Packet.hpp index d547990..b7c8a5f 100644 --- a/src/net/Packet.hpp +++ b/src/net/Packet.hpp @@ -16,8 +16,15 @@ struct Packet { LOGIN = 1, }; + struct TControl { + std::uint16_t seq; + std::uint16_t ack; + std::uint32_t hist; + }; + struct Header { std::uint32_t tag; + TControl ctrl; std::uint8_t type; } header; diff --git a/src/net/net.cpp b/src/net/net.cpp index 20b6f5c..314410e 100644 --- a/src/net/net.cpp +++ b/src/net/net.cpp @@ -75,7 +75,7 @@ void Client::HandlePacket(const UDPpacket &udp_pack) { return; } - conn.FlagRecv(); + conn.Received(udp_pack); cout << "I got something!" << endl; } @@ -102,7 +102,8 @@ void Client::SendLogin(const string &name) { Connection::Connection(const IPaddress &addr) : addr(addr) , send_timer(5000) -, recv_timer(10000) { +, recv_timer(10000) +, ctrl{ 0, 0xFFFF, 0xFFFF } { send_timer.Start(); recv_timer.Start(); } @@ -133,14 +134,53 @@ void Connection::Update(int dt) { } -void Connection::Send(UDPpacket &pack, UDPsocket sock) { - pack.address = addr; - if (SDLNet_UDP_Send(sock, -1, &pack) == 0) { +void Connection::Send(UDPpacket &udp_pack, UDPsocket sock) { + Packet &pack = *reinterpret_cast(udp_pack.data); + pack.header.ctrl = ctrl; + + udp_pack.address = addr; + if (SDLNet_UDP_Send(sock, -1, &udp_pack) == 0) { throw NetError("SDLNet_UDP_Send"); } + FlagSend(); } +void Connection::Received(const UDPpacket &udp_pack) { + Packet &pack = *reinterpret_cast(udp_pack.data); + + int diff = std::int16_t(pack.header.ctrl.seq) - std::int16_t(ctrl.ack); + + if (diff > 0) { + // incoming more recent than last acked + + // TODO: packets considered lost are detected here + // this should have ones for all of them: + // ~hist & ((1 << dist) - 1) if dist is < 32 + + if (diff >= 32) { + // missed more than the last 32 oO + ctrl.hist = 0; + } else { + ctrl.hist >>= diff; + ctrl.hist |= 1 << (32 - diff); + } + } else if (diff < 0) { + // incoming older than acked + if (diff > -32) { + // too late :/ + } else { + ctrl.hist |= 1 << (32 + diff); + } + } else { + // incoming the same as last acked oO + } + + ctrl.ack = pack.header.ctrl.seq; + + FlagRecv(); +} + void Connection::SendPing(UDPpacket &udp_pack, UDPsocket sock) { Packet &pack = *reinterpret_cast(udp_pack.data); udp_pack.len = pack.Ping(); @@ -230,7 +270,7 @@ void Server::HandlePacket(const UDPpacket &udp_pack) { } Connection &client = GetClient(udp_pack.address); - client.FlagRecv(); + client.Received(udp_pack); switch (pack.header.type) { case Packet::PING: -- 2.39.2 From da5073a7fdb1ca066e778a02db33d5d15073aea0 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Thu, 3 Sep 2015 10:39:52 +0200 Subject: [PATCH 07/16] defined and implemented join and part packets --- doc/protocol | 30 +++++++++++- src/app/Runtime.hpp | 1 - src/app/runtime.cpp | 8 ++-- src/net/Connection.hpp | 5 ++ src/net/Packet.hpp | 19 +++++++- src/net/Server.hpp | 1 + src/net/net.cpp | 101 +++++++++++++++++++++++++++++++++++------ src/world/World.hpp | 4 ++ 8 files changed, 145 insertions(+), 24 deletions(-) diff --git a/doc/protocol b/doc/protocol index 723f3cf..63055a2 100644 --- a/doc/protocol +++ b/doc/protocol @@ -21,5 +21,31 @@ been reached. Code: 1 Payload: - player name, max 32 byte UTF-8 string, - shorter names should be zero terminated + 0 player name, max 32 byte UTF-8 string, + shorter names should be zero terminated + + +Join +---- + +Sent by the server either as a response to a successful login or when +it's changing worlds. + +Code: 2 +Payload: + 0 entity ID of the player, 32bit unsigned int + 4 chunk coords of the player, 3x 32bit signed int + 16 pos/vel/rot/ang of the player, 13x 32bit float + 68 name of the world the server's currently running + max 32 byte UTF-8 string + + +Part +---- + +Sent by the server either as a respons to a failed login or when the +client was kicked. +Optionally sent by the client on disconnect. + +Code: 3 +Payload: none diff --git a/src/app/Runtime.hpp b/src/app/Runtime.hpp index ee43853..904431b 100644 --- a/src/app/Runtime.hpp +++ b/src/app/Runtime.hpp @@ -44,7 +44,6 @@ public: std::string asset_path; std::string save_path; - std::string world_name = "default"; Client::Config client = Client::Config(); Interface::Config interface = Interface::Config(); diff --git a/src/app/runtime.cpp b/src/app/runtime.cpp index 2d12863..e551e9d 100644 --- a/src/app/runtime.cpp +++ b/src/app/runtime.cpp @@ -164,7 +164,7 @@ void Runtime::ReadArgs(int argc, const char *const *argv) { cerr << "missing argument to --world-name" << endl; error = true; } else { - config.world_name = argv[i]; + config.world.name = argv[i]; } } else { cerr << "unknown option " << arg << endl; @@ -296,7 +296,7 @@ void Runtime::RunStandalone() { Environment env(init.window, config.asset_path); env.viewport.VSync(config.vsync); - WorldSave save(config.save_path + config.world_name + '/'); + WorldSave save(config.save_path + config.world.name + '/'); if (save.Exists()) { save.Read(config.world); } else { @@ -321,7 +321,7 @@ void Runtime::RunStandalone() { void Runtime::RunServer() { HeadlessEnvironment env(config.asset_path); - WorldSave save(config.save_path + config.world_name + '/'); + WorldSave save(config.save_path + config.world.name + '/'); if (save.Exists()) { save.Read(config.world); } else { @@ -340,7 +340,7 @@ void Runtime::RunClient() { Environment env(init.window, config.asset_path); env.viewport.VSync(config.vsync); - WorldSave save(config.save_path + config.world_name + '/'); + WorldSave save(config.save_path + config.world.name + '/'); if (save.Exists()) { save.Read(config.world); } else { diff --git a/src/net/Connection.hpp b/src/net/Connection.hpp index ade65eb..cc13ef6 100644 --- a/src/net/Connection.hpp +++ b/src/net/Connection.hpp @@ -22,6 +22,9 @@ public: bool ShouldPing() const noexcept; bool TimedOut() const noexcept; + void Close() noexcept { closed = true; } + bool Closed() const noexcept { return closed || TimedOut(); } + void Update(int dt); void SendPing(UDPpacket &, UDPsocket); @@ -40,6 +43,8 @@ private: Packet::TControl ctrl; + bool closed; + }; } diff --git a/src/net/Packet.hpp b/src/net/Packet.hpp index b7c8a5f..0d99fee 100644 --- a/src/net/Packet.hpp +++ b/src/net/Packet.hpp @@ -2,11 +2,14 @@ #define BLANK_NET_PACKET_HPP_ #include +#include #include namespace blank { +class Entity; + struct Packet { static constexpr std::uint32_t TAG = 0xFB1AB1AF; @@ -14,8 +17,12 @@ struct Packet { enum Type { PING = 0, LOGIN = 1, + JOIN = 2, + PART = 3, }; + static const char *Type2String(Type) noexcept; + struct TControl { std::uint16_t seq; std::uint16_t ack; @@ -31,13 +38,21 @@ struct Packet { std::uint8_t payload[500 - sizeof(Header)]; + Type GetType() const noexcept { return Type(header.type); } + void Tag() noexcept; - std::size_t Ping() noexcept; - std::size_t Login(const std::string &name) noexcept; + std::size_t MakePing() noexcept; + std::size_t MakeLogin(const std::string &name) noexcept; + std::size_t MakeJoin(const Entity &player, const std::string &world_name) noexcept; + std::size_t MakePart() noexcept; }; +inline std::ostream &operator <<(std::ostream &out, Packet::Type t) { + return out << Packet::Type2String(t); +} + } #endif diff --git a/src/net/Server.hpp b/src/net/Server.hpp index 6a1e996..a95cd37 100644 --- a/src/net/Server.hpp +++ b/src/net/Server.hpp @@ -34,6 +34,7 @@ private: void OnDisconnect(Connection &); void HandleLogin(Connection &client, const UDPpacket &); + void HandlePart(Connection &client, const UDPpacket &); private: UDPsocket serv_sock; diff --git a/src/net/net.cpp b/src/net/net.cpp index 314410e..9c29c1e 100644 --- a/src/net/net.cpp +++ b/src/net/net.cpp @@ -76,7 +76,6 @@ void Client::HandlePacket(const UDPpacket &udp_pack) { } conn.Received(udp_pack); - cout << "I got something!" << endl; } void Client::Update(int dt) { @@ -94,16 +93,17 @@ void Client::SendPing() { void Client::SendLogin(const string &name) { Packet &pack = *reinterpret_cast(client_pack.data); - client_pack.len = pack.Login(name); + client_pack.len = pack.MakeLogin(name); conn.Send(client_pack, client_sock); } Connection::Connection(const IPaddress &addr) : addr(addr) -, send_timer(5000) +, send_timer(3000) , recv_timer(10000) -, ctrl{ 0, 0xFFFF, 0xFFFF } { +, ctrl{ 0, 0xFFFF, 0xFFFF } +, closed(false) { send_timer.Start(); recv_timer.Start(); } @@ -138,6 +138,8 @@ void Connection::Send(UDPpacket &udp_pack, UDPsocket sock) { Packet &pack = *reinterpret_cast(udp_pack.data); pack.header.ctrl = ctrl; + cout << "sending " << pack.GetType() << " to " << Address() << endl; + udp_pack.address = addr; if (SDLNet_UDP_Send(sock, -1, &udp_pack) == 0) { throw NetError("SDLNet_UDP_Send"); @@ -149,6 +151,8 @@ void Connection::Send(UDPpacket &udp_pack, UDPsocket sock) { void Connection::Received(const UDPpacket &udp_pack) { Packet &pack = *reinterpret_cast(udp_pack.data); + cout << "received " << pack.GetType() << " from " << Address() << endl; + int diff = std::int16_t(pack.header.ctrl.seq) - std::int16_t(ctrl.ack); if (diff > 0) { @@ -183,7 +187,7 @@ void Connection::Received(const UDPpacket &udp_pack) { void Connection::SendPing(UDPpacket &udp_pack, UDPsocket sock) { Packet &pack = *reinterpret_cast(udp_pack.data); - udp_pack.len = pack.Ping(); + udp_pack.len = pack.MakePing(); Send(udp_pack, sock); } @@ -201,17 +205,32 @@ ostream &operator <<(ostream &out, const IPaddress &addr) { } +const char *Packet::Type2String(Type t) noexcept { + switch (t) { + case PING: + return "PING"; + case LOGIN: + return "LOGIN"; + case JOIN: + return "JOIN"; + case PART: + return "PART"; + default: + return "UNKNOWN"; + } +} + void Packet::Tag() noexcept { header.tag = TAG; } -size_t Packet::Ping() noexcept { +size_t Packet::MakePing() noexcept { Tag(); header.type = PING; return sizeof(Header); } -size_t Packet::Login(const string &name) noexcept { +size_t Packet::MakeLogin(const string &name) noexcept { constexpr size_t maxname = 32; Tag(); @@ -225,6 +244,48 @@ size_t Packet::Login(const string &name) noexcept { return sizeof(Header) + maxname; } +size_t Packet::MakeJoin(const Entity &player, const string &world_name) noexcept { + constexpr size_t maxname = 32; + + Tag(); + header.type = JOIN; + + uint8_t *cursor = &payload[0]; + + // TODO: generate entity IDs + *reinterpret_cast(cursor) = 1; + cursor += 4; + + *reinterpret_cast(cursor) = player.ChunkCoords(); + cursor += 12; + + *reinterpret_cast(cursor) = player.Position(); + cursor += 12; + *reinterpret_cast(cursor) = player.Velocity(); + cursor += 12; + + *reinterpret_cast(cursor) = player.Orientation(); + cursor += 16; + *reinterpret_cast(cursor) = player.AngularVelocity(); + cursor += 12; + + if (world_name.size() < maxname) { + memset(cursor, '\0', maxname); + memcpy(cursor, world_name.c_str(), world_name.size()); + } else { + memcpy(cursor, world_name.c_str(), maxname); + } + cursor += maxname; + + return sizeof(Header) + (cursor - &payload[0]); +} + +size_t Packet::MakePart() noexcept { + Tag(); + header.type = PART; + return sizeof(Header); +} + Server::Server(const Config &conf, World &world) : serv_sock(nullptr) @@ -273,14 +334,14 @@ void Server::HandlePacket(const UDPpacket &udp_pack) { client.Received(udp_pack); switch (pack.header.type) { - case Packet::PING: - // already done all that's supposed to do - break; case Packet::LOGIN: HandleLogin(client, udp_pack); break; + case Packet::PART: + HandlePart(client, udp_pack); + break; default: - // just drop packets of unknown type + // just drop packets of unknown or unhandled type break; } } @@ -305,7 +366,7 @@ void Server::OnConnect(Connection &client) { void Server::Update(int dt) { for (list::iterator client(clients.begin()), end(clients.end()); client != end;) { client->Update(dt); - if (client->TimedOut()) { + if (client->Closed()) { OnDisconnect(*client); client = clients.erase(client); } else { @@ -330,16 +391,26 @@ void Server::HandleLogin(Connection &client, const UDPpacket &udp_pack) { for (size_t i = 0; i < maxlen && pack.payload[i] != '\0'; ++i) { name.push_back(pack.payload[i]); } - cout << "got login request from player \"" << name << '"' << endl; Entity *player = world.AddPlayer(name); + Packet &response = *reinterpret_cast(serv_pack.data); + if (player) { // success! - cout << "\taccepted" << endl; + cout << "accepted login from player \"" << name << '"' << endl; + response.MakeJoin(*player, world.Name()); + client.Send(serv_pack, serv_sock); } else { // aw no :( - cout << "\trejected" << endl; + cout << "rejected login from player \"" << name << '"' << endl; + response.MakePart(); + client.Send(serv_pack, serv_sock); + client.Close(); } } +void Server::HandlePart(Connection &client, const UDPpacket &udp_pack) { + client.Close(); +} + } diff --git a/src/world/World.hpp b/src/world/World.hpp index 3981b28..7d2c292 100644 --- a/src/world/World.hpp +++ b/src/world/World.hpp @@ -6,6 +6,7 @@ #include "Generator.hpp" #include +#include #include #include @@ -21,6 +22,7 @@ class World { public: struct Config { + std::string name = "default"; // initial player position glm::vec3 spawn = { 0.0f, 0.0f, 0.0f }; // direction facing towards(!) the light @@ -37,6 +39,8 @@ public: World(const BlockTypeRegistry &, const Config &, const WorldSave &); + const std::string &Name() const noexcept { return config.name; } + /// check if this ray hits a block /// depth in the collision is the distance between the ray's /// origin and the intersection point -- 2.39.2 From 1afc887a2040dfdedfa66913e94ff7a9634f648f Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Fri, 4 Sep 2015 10:42:45 +0200 Subject: [PATCH 08/16] reorganized client state --- src/app/ClientState.cpp | 94 --------- src/app/Environment.hpp | 17 +- src/app/Runtime.hpp | 5 +- src/app/StateControl.hpp | 17 ++ src/app/app.cpp | 11 + src/app/runtime.cpp | 89 ++++---- src/client/InitialState.hpp | 34 +++ .../InteractiveState.hpp} | 30 +-- src/client/MasterState.hpp | 67 ++++++ src/client/client.cpp | 188 +++++++++++++++++ src/net/Client.hpp | 6 +- src/net/Connection.hpp | 8 + src/net/Packet.hpp | 100 +++++++-- src/net/PacketHandler.hpp | 26 +++ src/net/net.cpp | 197 +++++++++++------- 15 files changed, 630 insertions(+), 259 deletions(-) delete mode 100644 src/app/ClientState.cpp create mode 100644 src/client/InitialState.hpp rename src/{app/ClientState.hpp => client/InteractiveState.hpp} (54%) create mode 100644 src/client/MasterState.hpp create mode 100644 src/client/client.cpp create mode 100644 src/net/PacketHandler.hpp diff --git a/src/app/ClientState.cpp b/src/app/ClientState.cpp deleted file mode 100644 index b4ed3f8..0000000 --- a/src/app/ClientState.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include "ClientState.hpp" - -#include "Environment.hpp" -#include "init.hpp" -#include "TextureIndex.hpp" - -namespace blank { - -ClientState::ClientState( - Environment &env, - const World::Config &wc, - const WorldSave &ws, - const Interface::Config &ic, - const Client::Config &cc -) -: env(env) -, block_types() -, world(block_types, wc, ws) -, chunk_renderer(world, wc.load.load_dist) -, interface(ic, env, world) -, client(cc, world) { - TextureIndex tex_index; - env.loader.LoadBlockTypes("default", block_types, tex_index); - chunk_renderer.LoadTextures(env.loader, tex_index); - chunk_renderer.FogDensity(wc.fog_density); - // TODO: better solution for initializing HUD - interface.SelectNext(); - client.SendLogin(ic.player_name); -} - - -void ClientState::OnEnter() { - env.window.GrabMouse(); -} - - -void ClientState::Handle(const SDL_Event &event) { - switch (event.type) { - case SDL_KEYDOWN: - interface.HandlePress(event.key); - break; - case SDL_KEYUP: - interface.HandleRelease(event.key); - break; - case SDL_MOUSEBUTTONDOWN: - interface.HandlePress(event.button); - break; - case SDL_MOUSEBUTTONUP: - interface.HandleRelease(event.button); - break; - case SDL_MOUSEMOTION: - interface.Handle(event.motion); - break; - case SDL_MOUSEWHEEL: - interface.Handle(event.wheel); - break; - case SDL_QUIT: - env.state.Pop(); - break; - default: - break; - } -} - - -void ClientState::Update(int dt) { - client.Handle(); - client.Update(dt); - if (client.TimedOut()) { - env.state.Pop(); - } - - interface.Update(dt); - world.Update(dt); - chunk_renderer.Rebase(interface.Player().ChunkCoords()); - chunk_renderer.Update(dt); - - glm::mat4 trans = interface.Player().Transform(interface.Player().ChunkCoords()); - glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f)); - glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)); - env.audio.Position(interface.Player().Position()); - env.audio.Velocity(interface.Player().Velocity()); - env.audio.Orientation(dir, up); -} - - -void ClientState::Render(Viewport &viewport) { - viewport.WorldPosition(interface.Player().Transform(interface.Player().ChunkCoords())); - chunk_renderer.Render(viewport); - world.Render(viewport); - interface.Render(viewport); -} - -} diff --git a/src/app/Environment.hpp b/src/app/Environment.hpp index 95813fc..97f4a7e 100644 --- a/src/app/Environment.hpp +++ b/src/app/Environment.hpp @@ -17,6 +17,19 @@ class Window; struct HeadlessEnvironment { + struct Config { + std::string asset_path; + std::string save_path; + + std::string GetWorldPath( + const std::string &world_name + ) const; + std::string GetWorldPath( + const std::string &world_name, + const std::string &hostname + ) const; + } config; + AssetLoader loader; FrameCounter counter; @@ -24,7 +37,7 @@ struct HeadlessEnvironment { StateControl state; - explicit HeadlessEnvironment(const std::string &asset_path); + explicit HeadlessEnvironment(const Config &); }; @@ -41,7 +54,7 @@ struct Environment Keymap keymap; - Environment(Window &win, const std::string &asset_path); + Environment(Window &win, const Config &); }; diff --git a/src/app/Runtime.hpp b/src/app/Runtime.hpp index 904431b..10a0e55 100644 --- a/src/app/Runtime.hpp +++ b/src/app/Runtime.hpp @@ -1,6 +1,7 @@ #ifndef BLANK_RUNTIME_HPP_ #define BLANK_RUNTIME_HPP_ +#include "Environment.hpp" #include "../net/Client.hpp" #include "../net/Server.hpp" #include "../ui/Interface.hpp" @@ -42,10 +43,8 @@ public: bool doublebuf = true; int multisampling = 1; - std::string asset_path; - std::string save_path; - Client::Config client = Client::Config(); + HeadlessEnvironment::Config env = HeadlessEnvironment::Config(); Interface::Config interface = Interface::Config(); Server::Config server = Server::Config(); World::Config world = World::Config(); diff --git a/src/app/StateControl.hpp b/src/app/StateControl.hpp index c072040..d679a53 100644 --- a/src/app/StateControl.hpp +++ b/src/app/StateControl.hpp @@ -12,22 +12,37 @@ class State; class StateControl { public: + // add state to the front void Push(State *s) { cue.emplace(PUSH, s); } + // swap state at the front void Switch(State *s) { cue.emplace(SWITCH, s); } + // remove state at the front void Pop() { cue.emplace(POP); } + // remove all states + // application will exit if nothing is pushed after this void PopAll() { cue.emplace(POP_ALL); } + // pop states until this one is on top + void PopAfter(State *s) { + cue.emplace(POP_AFTER, s); + } + + // pop states until this one is removed + void PopUntil(State *s) { + cue.emplace(POP_UNTIL, s); + } + void Commit(HeadlessApplication &); @@ -37,6 +52,8 @@ private: SWITCH, POP, POP_ALL, + POP_AFTER, + POP_UNTIL, }; struct Memo { State *state; diff --git a/src/app/app.cpp b/src/app/app.cpp index 48a5950..4368022 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -270,6 +270,17 @@ void StateControl::Commit(HeadlessApplication &app) { app.PopState(); } break; + case POP_AFTER: + while (app.HasState() && &app.GetState() != m.state) { + app.PopState(); + } + break; + case POP_UNTIL: + while (app.HasState()) { + if (app.PopState() == m.state) { + break; + } + } } } } diff --git a/src/app/runtime.cpp b/src/app/runtime.cpp index e551e9d..e169a7b 100644 --- a/src/app/runtime.cpp +++ b/src/app/runtime.cpp @@ -1,11 +1,11 @@ #include "Application.hpp" -#include "ClientState.hpp" #include "Environment.hpp" #include "Runtime.hpp" #include "ServerState.hpp" #include "WorldState.hpp" #include "init.hpp" +#include "../client/MasterState.hpp" #include "../io/filesystem.hpp" #include "../io/WorldSave.hpp" @@ -47,15 +47,24 @@ string default_save_path() { namespace blank { -HeadlessEnvironment::HeadlessEnvironment(const string &asset_path) -: loader(asset_path) +HeadlessEnvironment::HeadlessEnvironment(const Config &config) +: config(config) +, loader(config.asset_path) , counter() , state() { } -Environment::Environment(Window &win, const string &asset_path) -: HeadlessEnvironment(asset_path) +string HeadlessEnvironment::Config::GetWorldPath(const string &world_name) const { + return save_path + "worlds/" + world_name + '/'; +} + +string HeadlessEnvironment::Config::GetWorldPath(const string &world_name, const string &host_name) const { + return save_path + "cache/" + host_name + '/' + world_name + '/'; +} + +Environment::Environment(Window &win, const Config &config) +: HeadlessEnvironment(config) , assets(loader) , audio() , viewport() @@ -64,6 +73,15 @@ Environment::Environment(Window &win, const string &asset_path) viewport.Clear(); window.Flip(); keymap.LoadDefault(); + + string keys_path = config.save_path + "keys.conf"; + if (!is_file(keys_path)) { + std::ofstream file(keys_path); + keymap.Save(file); + } else { + std::ifstream file(keys_path); + keymap.Load(file); + } } @@ -123,7 +141,7 @@ void Runtime::ReadArgs(int argc, const char *const *argv) { cerr << "missing argument to --asset-path" << endl; error = true; } else { - config.asset_path = argv[i]; + config.env.asset_path = argv[i]; } } else if (strcmp(param, "host") == 0) { ++i; @@ -156,7 +174,7 @@ void Runtime::ReadArgs(int argc, const char *const *argv) { cerr << "missing argument to --save-path" << endl; error = true; } else { - config.save_path = argv[i]; + config.env.save_path = argv[i]; } } else if (strcmp(param, "world-name") == 0) { ++i; @@ -237,21 +255,21 @@ void Runtime::ReadArgs(int argc, const char *const *argv) { return; } - if (config.asset_path.empty()) { - config.asset_path = default_asset_path(); + if (config.env.asset_path.empty()) { + config.env.asset_path = default_asset_path(); } else if ( - config.asset_path[config.asset_path.size() - 1] != '/' && - config.asset_path[config.asset_path.size() - 1] != '\\' + config.env.asset_path[config.env.asset_path.size() - 1] != '/' && + config.env.asset_path[config.env.asset_path.size() - 1] != '\\' ) { - config.asset_path += '/'; + config.env.asset_path += '/'; } - if (config.save_path.empty()) { - config.save_path = default_save_path(); + if (config.env.save_path.empty()) { + config.env.save_path = default_save_path(); } else if ( - config.save_path[config.save_path.size() - 1] != '/' && - config.save_path[config.save_path.size() - 1] != '\\' + config.env.save_path[config.env.save_path.size() - 1] != '/' && + config.env.save_path[config.env.save_path.size() - 1] != '\\' ) { - config.save_path += '/'; + config.env.save_path += '/'; } if (n > 0) { @@ -293,25 +311,16 @@ int Runtime::Execute() { void Runtime::RunStandalone() { Init init(config.doublebuf, config.multisampling); - Environment env(init.window, config.asset_path); + Environment env(init.window, config.env); env.viewport.VSync(config.vsync); - WorldSave save(config.save_path + config.world.name + '/'); + WorldSave save(config.env.GetWorldPath(config.world.name)); if (save.Exists()) { save.Read(config.world); } else { save.Write(config.world); } - std::string keys_path = config.save_path + "keys.conf"; - if (!is_file(keys_path)) { - std::ofstream file(keys_path); - env.keymap.Save(file); - } else { - std::ifstream file(keys_path); - env.keymap.Load(file); - } - Application app(env); WorldState world_state(env, config.interface, config.world, save); app.PushState(&world_state); @@ -319,9 +328,9 @@ void Runtime::RunStandalone() { } void Runtime::RunServer() { - HeadlessEnvironment env(config.asset_path); + HeadlessEnvironment env(config.env); - WorldSave save(config.save_path + config.world.name + '/'); + WorldSave save(config.env.GetWorldPath(config.world.name)); if (save.Exists()) { save.Read(config.world); } else { @@ -337,27 +346,11 @@ void Runtime::RunServer() { void Runtime::RunClient() { Init init(config.doublebuf, config.multisampling); - Environment env(init.window, config.asset_path); + Environment env(init.window, config.env); env.viewport.VSync(config.vsync); - WorldSave save(config.save_path + config.world.name + '/'); - if (save.Exists()) { - save.Read(config.world); - } else { - save.Write(config.world); - } - - std::string keys_path = config.save_path + "keys.conf"; - if (!is_file(keys_path)) { - std::ofstream file(keys_path); - env.keymap.Save(file); - } else { - std::ifstream file(keys_path); - env.keymap.Load(file); - } - Application app(env); - ClientState client_state(env, config.world, save, config.interface, config.client); + client::MasterState client_state(env, config.world, config.interface, config.client); app.PushState(&client_state); Run(app); } diff --git a/src/client/InitialState.hpp b/src/client/InitialState.hpp new file mode 100644 index 0000000..05e457d --- /dev/null +++ b/src/client/InitialState.hpp @@ -0,0 +1,34 @@ +#ifndef BLANK_CLIENT_INITIALSTATE_HPP_ +#define BLANK_CLIENT_INITIALSTATE_HPP_ + +#include "../app/State.hpp" +#include "../ui/FixedText.hpp" + + +namespace blank { +namespace client { + +class MasterState; + +class InitialState +: public State { + +public: + explicit InitialState(MasterState &); + + void OnEnter() override; + + void Handle(const SDL_Event &) override; + void Update(int dt) override; + void Render(Viewport &) override; + +private: + MasterState &master; + FixedText message; + +}; + +} +} + +#endif diff --git a/src/app/ClientState.hpp b/src/client/InteractiveState.hpp similarity index 54% rename from src/app/ClientState.hpp rename to src/client/InteractiveState.hpp index 8868c98..e679eb2 100644 --- a/src/app/ClientState.hpp +++ b/src/client/InteractiveState.hpp @@ -1,8 +1,8 @@ -#ifndef BLANK_APP_CLIENTSTATE_HPP_ -#define BLANK_APP_CLIENTSTATE_HPP_ +#ifndef BLANK_CLIENT_INTERACTIVESTATE_HPP_ +#define BLANK_CLIENT_INTERACTIVESTATE_HPP_ -#include "State.hpp" -#include "../net/Client.hpp" +#include "../app/State.hpp" +#include "../io/WorldSave.hpp" #include "../ui/Interface.hpp" #include "../world/BlockTypeRegistry.hpp" #include "../world/ChunkRenderer.hpp" @@ -13,17 +13,18 @@ namespace blank { class Environment; -class ClientState +namespace client { + +class MasterState; + +class InteractiveState : public State { public: - ClientState( - Environment &, - const World::Config &, - const WorldSave &, - const Interface::Config &, - const Client::Config & - ); + explicit InteractiveState(MasterState &); + + World &GetWorld() noexcept { return world; } + Interface &GetInterface() noexcept { return interface; } void OnEnter() override; @@ -32,15 +33,16 @@ public: void Render(Viewport &) override; private: - Environment &env; + MasterState &master; BlockTypeRegistry block_types; + WorldSave save; World world; ChunkRenderer chunk_renderer; Interface interface; - Client client; }; +} } #endif diff --git a/src/client/MasterState.hpp b/src/client/MasterState.hpp new file mode 100644 index 0000000..af2e6aa --- /dev/null +++ b/src/client/MasterState.hpp @@ -0,0 +1,67 @@ +#ifndef BLANK_CLIENT_CLIENTSTATE_HPP_ +#define BLANK_CLIENT_CLIENTSTATE_HPP_ + +#include "InitialState.hpp" +#include "InteractiveState.hpp" +#include "../app/State.hpp" +#include "../net/Client.hpp" +#include "../net/PacketHandler.hpp" + +#include + + +namespace blank { + +class Environment; + +namespace client { + +class InteractiveState; + +class MasterState +: public State +, public PacketHandler { + +public: + MasterState( + Environment &, + const World::Config &, + const Interface::Config &, + const Client::Config & + ); + + Client &GetClient() noexcept { return client; } + Environment &GetEnv() noexcept { return env; } + + World::Config &GetWorldConf() noexcept { return world_conf; } + const World::Config &GetWorldConf() const noexcept { return world_conf; } + const Interface::Config &GetInterfaceConf() const noexcept { return intf_conf; } + const Client::Config &GetClientConf() const noexcept { return client_conf; } + + void Quit(); + + void OnEnter() override; + + void Handle(const SDL_Event &) override; + void Update(int dt) override; + void Render(Viewport &) override; + + void On(const Packet::Join &) override; + void On(const Packet::Part &) override; + +private: + Environment &env; + World::Config world_conf; + const Interface::Config &intf_conf; + const Client::Config &client_conf; + std::unique_ptr state; + Client client; + + InitialState init_state; + +}; + +} +} + +#endif diff --git a/src/client/client.cpp b/src/client/client.cpp new file mode 100644 index 0000000..ac682bb --- /dev/null +++ b/src/client/client.cpp @@ -0,0 +1,188 @@ +#include "InitialState.hpp" +#include "InteractiveState.hpp" +#include "MasterState.hpp" + +#include "../app/Environment.hpp" +#include "../app/init.hpp" +#include "../app/TextureIndex.hpp" + +#include + + +namespace blank { +namespace client { + +InitialState::InitialState(MasterState &master) +: master(master) +, message() { + message.Position(glm::vec3(0.0f), Gravity::CENTER); + message.Set(master.GetEnv().assets.large_ui_font, "logging in"); +} + +void InitialState::OnEnter() { + +} + +void InitialState::Handle(const SDL_Event &evt) { + if (evt.type == SDL_QUIT) { + master.Quit(); + } +} + +void InitialState::Update(int dt) { + master.Update(dt); +} + +void InitialState::Render(Viewport &viewport) { + message.Render(viewport); +} + + +InteractiveState::InteractiveState(MasterState &master) +: master(master) +, block_types() +, save(master.GetEnv().config.GetWorldPath(master.GetWorldConf().name, master.GetClientConf().host)) +, world(block_types, master.GetWorldConf(), save) +, chunk_renderer(world, master.GetWorldConf().load.load_dist) +, interface(master.GetInterfaceConf(), master.GetEnv(), world) { + TextureIndex tex_index; + master.GetEnv().loader.LoadBlockTypes("default", block_types, tex_index); + chunk_renderer.LoadTextures(master.GetEnv().loader, tex_index); + chunk_renderer.FogDensity(master.GetWorldConf().fog_density); + // TODO: better solution for initializing HUD + interface.SelectNext(); +} + +void InteractiveState::OnEnter() { + master.GetEnv().window.GrabMouse(); +} + +void InteractiveState::Handle(const SDL_Event &event) { + switch (event.type) { + case SDL_KEYDOWN: + interface.HandlePress(event.key); + break; + case SDL_KEYUP: + interface.HandleRelease(event.key); + break; + case SDL_MOUSEBUTTONDOWN: + interface.HandlePress(event.button); + break; + case SDL_MOUSEBUTTONUP: + interface.HandleRelease(event.button); + break; + case SDL_MOUSEMOTION: + interface.Handle(event.motion); + break; + case SDL_MOUSEWHEEL: + interface.Handle(event.wheel); + break; + case SDL_QUIT: + master.Quit(); + break; + default: + break; + } +} + +void InteractiveState::Update(int dt) { + master.Update(dt); + + interface.Update(dt); + world.Update(dt); + chunk_renderer.Rebase(interface.Player().ChunkCoords()); + chunk_renderer.Update(dt); + + glm::mat4 trans = interface.Player().Transform(interface.Player().ChunkCoords()); + glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f)); + glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)); + master.GetEnv().audio.Position(interface.Player().Position()); + master.GetEnv().audio.Velocity(interface.Player().Velocity()); + master.GetEnv().audio.Orientation(dir, up); +} + +void InteractiveState::Render(Viewport &viewport) { + viewport.WorldPosition(interface.Player().Transform(interface.Player().ChunkCoords())); + chunk_renderer.Render(viewport); + world.Render(viewport); + interface.Render(viewport); +} + + +MasterState::MasterState( + Environment &env, + const World::Config &wc, + const Interface::Config &ic, + const Client::Config &cc) +: env(env) +, world_conf(wc) +, intf_conf(ic) +, client_conf(cc) +, state() +, client(cc) +, init_state(*this) { + client.GetConnection().SetHandler(this); +} + +void MasterState::Quit() { + env.state.PopUntil(this); +} + + +void MasterState::OnEnter() { + client.SendLogin(intf_conf.player_name); + env.state.Push(&init_state); +} + + +void MasterState::Handle(const SDL_Event &event) { + +} + + +void MasterState::Update(int dt) { + client.Handle(); + client.Update(dt); + if (client.GetConnection().Closed()) { + Quit(); + // TODO: push disconnected message + } +} + + +void MasterState::Render(Viewport &) { + +} + + +void MasterState::On(const Packet::Join &pack) { + pack.ReadWorldName(world_conf.name); + + if (state) { + // changing worlds + std::cout << "server changing worlds" << std::endl; + } else { + // joining game + std::cout << "joined game" << std::endl; + } + state.reset(new InteractiveState(*this)); + + pack.ReadPlayer(state->GetInterface().Player()); + + env.state.PopAfter(this); + env.state.Push(state.get()); +} + +void MasterState::On(const Packet::Part &pack) { + if (state) { + // kicked + std::cout << "kicked by server" << std::endl; + } else { + // join refused + std::cout << "login refused by server" << std::endl; + } + Quit(); +} + +} +} diff --git a/src/net/Client.hpp b/src/net/Client.hpp index 5b83061..3960e84 100644 --- a/src/net/Client.hpp +++ b/src/net/Client.hpp @@ -20,14 +20,15 @@ public: }; public: - Client(const Config &, World &); + explicit Client(const Config &); ~Client(); void Handle(); void Update(int dt); - bool TimedOut() { return conn.TimedOut(); } + Connection &GetConnection() noexcept { return conn; } + const Connection &GetConnection() const noexcept { return conn; } void SendPing(); void SendLogin(const std::string &); @@ -36,7 +37,6 @@ private: void HandlePacket(const UDPpacket &); private: - World &world; Connection conn; UDPsocket client_sock; UDPpacket client_pack; diff --git a/src/net/Connection.hpp b/src/net/Connection.hpp index cc13ef6..58f315d 100644 --- a/src/net/Connection.hpp +++ b/src/net/Connection.hpp @@ -10,11 +10,18 @@ namespace blank { +class PacketHandler; + class Connection { public: explicit Connection(const IPaddress &); + void SetHandler(PacketHandler *h) noexcept { handler = h; } + void RemoveHandler() noexcept { handler = nullptr; } + bool HasHandler() const noexcept { return handler; } + PacketHandler &Handler() noexcept { return *handler; } + const IPaddress &Address() const noexcept { return addr; } bool Matches(const IPaddress &) const noexcept; @@ -37,6 +44,7 @@ private: void FlagRecv() noexcept; private: + PacketHandler *handler; IPaddress addr; IntervalTimer send_timer; IntervalTimer recv_timer; diff --git a/src/net/Packet.hpp b/src/net/Packet.hpp index 0d99fee..bd9dd86 100644 --- a/src/net/Packet.hpp +++ b/src/net/Packet.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace blank { @@ -14,14 +15,7 @@ struct Packet { static constexpr std::uint32_t TAG = 0xFB1AB1AF; - enum Type { - PING = 0, - LOGIN = 1, - JOIN = 2, - PART = 3, - }; - - static const char *Type2String(Type) noexcept; + static const char *Type2String(std::uint8_t) noexcept; struct TControl { std::uint16_t seq; @@ -35,23 +29,91 @@ struct Packet { std::uint8_t type; } header; - std::uint8_t payload[500 - sizeof(Header)]; + static constexpr std::size_t MAX_PAYLOAD_LEN = 500 - sizeof(Header); + std::uint8_t payload[MAX_PAYLOAD_LEN]; - Type GetType() const noexcept { return Type(header.type); } - void Tag() noexcept; + void Tag() noexcept { header.tag = TAG; } - std::size_t MakePing() noexcept; - std::size_t MakeLogin(const std::string &name) noexcept; - std::size_t MakeJoin(const Entity &player, const std::string &world_name) noexcept; - std::size_t MakePart() noexcept; + void Type(std::uint8_t t) noexcept { header.type = t; } + std::uint8_t Type() const noexcept { return header.type; } + const char *TypeString() const noexcept { return Type2String(Type()); } -}; -inline std::ostream &operator <<(std::ostream &out, Packet::Type t) { - return out << Packet::Type2String(t); -} + struct Payload { + std::size_t length; + std::uint8_t *data; + + template + void Write(const T &, size_t off) noexcept; + template + void Read(T &, size_t off) const noexcept; + + void WriteString(const std::string &src, std::size_t off, std::size_t maxlen) noexcept; + void ReadString(std::string &dst, std::size_t off, std::size_t maxlen) const noexcept; + }; + + struct Ping : public Payload { + static constexpr std::uint8_t TYPE = 0; + static constexpr std::size_t MAX_LEN = 0; + }; + + struct Login : public Payload { + static constexpr std::uint8_t TYPE = 1; + static constexpr std::size_t MAX_LEN = 32; + + void WritePlayerName(const std::string &) noexcept; + void ReadPlayerName(std::string &) const noexcept; + }; + + struct Join : public Payload { + static constexpr std::uint8_t TYPE = 2; + static constexpr std::size_t MAX_LEN = 100; + + void WritePlayer(const Entity &) noexcept; + void ReadPlayer(Entity &) const noexcept; + void WriteWorldName(const std::string &) noexcept; + void ReadWorldName(std::string &) const noexcept; + }; + + struct Part : public Payload { + static constexpr std::uint8_t TYPE = 3; + static constexpr std::size_t MAX_LEN = 0; + }; + + + template + PayloadType As() { + PayloadType result; + result.length = PayloadType::MAX_LEN; + result.data = &payload[0]; + return result; + } + + template + static PayloadType As(const UDPpacket &pack) { + PayloadType result; + result.length = std::min(pack.len - sizeof(Header), PayloadType::MAX_LEN); + result.data = pack.data + sizeof(Header); + return result; + } + + template + static PayloadType Make(UDPpacket &udp_pack) { + Packet &pack = *reinterpret_cast(udp_pack.data); + pack.Tag(); + pack.Type(PayloadType::TYPE); + + udp_pack.len = sizeof(Header) + PayloadType::TYPE; + + PayloadType result; + result.length = PayloadType::MAX_LEN; + result.data = pack.payload; + return result; + } + +}; } diff --git a/src/net/PacketHandler.hpp b/src/net/PacketHandler.hpp new file mode 100644 index 0000000..0ea9b57 --- /dev/null +++ b/src/net/PacketHandler.hpp @@ -0,0 +1,26 @@ +#ifndef BLANK_NET_PACKETHANDLER_HPP_ +#define BLANK_NET_PACKETHANDLER_HPP_ + +#include "Packet.hpp" + +#include + + +namespace blank { + +class PacketHandler { + +public: + void Handle(const UDPpacket &); + +private: + virtual void On(const Packet::Ping &) { } + virtual void On(const Packet::Login &) { } + virtual void On(const Packet::Join &) { } + virtual void On(const Packet::Part &) { } + +}; + +} + +#endif diff --git a/src/net/net.cpp b/src/net/net.cpp index 9c29c1e..1be712b 100644 --- a/src/net/net.cpp +++ b/src/net/net.cpp @@ -2,6 +2,7 @@ #include "Connection.hpp" #include "io.hpp" #include "Packet.hpp" +#include "PacketHandler.hpp" #include "Server.hpp" #include "../app/init.hpp" @@ -35,9 +36,8 @@ IPaddress client_resolve(const char *host, Uint16 port) { } -Client::Client(const Config &conf, World &world) -: world(world) -, conn(client_resolve(conf.host.c_str(), conf.port)) +Client::Client(const Config &conf) +: conn(client_resolve(conf.host.c_str(), conf.port)) , client_sock(client_bind(0)) , client_pack{ -1, nullptr, 0 } { client_pack.data = new Uint8[sizeof(Packet)]; @@ -92,14 +92,15 @@ void Client::SendPing() { } void Client::SendLogin(const string &name) { - Packet &pack = *reinterpret_cast(client_pack.data); - client_pack.len = pack.MakeLogin(name); + auto pack = Packet::Make(client_pack); + pack.WritePlayerName(name); conn.Send(client_pack, client_sock); } Connection::Connection(const IPaddress &addr) -: addr(addr) +: handler(nullptr) +, addr(addr) , send_timer(3000) , recv_timer(10000) , ctrl{ 0, 0xFFFF, 0xFFFF } @@ -137,8 +138,9 @@ void Connection::Update(int dt) { void Connection::Send(UDPpacket &udp_pack, UDPsocket sock) { Packet &pack = *reinterpret_cast(udp_pack.data); pack.header.ctrl = ctrl; + ++ctrl.seq; - cout << "sending " << pack.GetType() << " to " << Address() << endl; + cout << "sending " << pack.TypeString() << " to " << Address() << endl; udp_pack.address = addr; if (SDLNet_UDP_Send(sock, -1, &udp_pack) == 0) { @@ -151,7 +153,7 @@ void Connection::Send(UDPpacket &udp_pack, UDPsocket sock) { void Connection::Received(const UDPpacket &udp_pack) { Packet &pack = *reinterpret_cast(udp_pack.data); - cout << "received " << pack.GetType() << " from " << Address() << endl; + cout << "received " << pack.TypeString() << " from " << Address() << endl; int diff = std::int16_t(pack.header.ctrl.seq) - std::int16_t(ctrl.ack); @@ -182,12 +184,15 @@ void Connection::Received(const UDPpacket &udp_pack) { ctrl.ack = pack.header.ctrl.seq; + if (HasHandler()) { + Handler().Handle(udp_pack); + } + FlagRecv(); } void Connection::SendPing(UDPpacket &udp_pack, UDPsocket sock) { - Packet &pack = *reinterpret_cast(udp_pack.data); - udp_pack.len = pack.MakePing(); + Packet::Make(udp_pack); Send(udp_pack, sock); } @@ -205,85 +210,127 @@ ostream &operator <<(ostream &out, const IPaddress &addr) { } -const char *Packet::Type2String(Type t) noexcept { +const char *Packet::Type2String(uint8_t t) noexcept { switch (t) { - case PING: - return "PING"; - case LOGIN: - return "LOGIN"; - case JOIN: - return "JOIN"; - case PART: - return "PART"; + case Ping::TYPE: + return "Ping"; + case Login::TYPE: + return "Login"; + case Join::TYPE: + return "Join"; + case Part::TYPE: + return "Part"; default: - return "UNKNOWN"; + return "Unknown"; } } -void Packet::Tag() noexcept { - header.tag = TAG; +template +void Packet::Payload::Write(const T &src, size_t off) noexcept { + if ((length - off) < sizeof(T)) { + // dismiss out of bounds write + return; + } + *reinterpret_cast(&data[off]) = src; } -size_t Packet::MakePing() noexcept { - Tag(); - header.type = PING; - return sizeof(Header); +template +void Packet::Payload::Read(T &dst, size_t off) const noexcept { + if ((length - off) < sizeof(T)) { + // dismiss out of bounds read + return; + } + dst = *reinterpret_cast(&data[off]); } -size_t Packet::MakeLogin(const string &name) noexcept { - constexpr size_t maxname = 32; - - Tag(); - header.type = LOGIN; - if (name.size() < maxname) { - memset(payload, '\0', maxname); - memcpy(payload, name.c_str(), name.size()); +void Packet::Payload::WriteString(const string &src, size_t off, size_t maxlen) noexcept { + uint8_t *dst = &data[off]; + size_t len = min(maxlen, length - off); + if (src.size() < len) { + memset(dst, '\0', len); + memcpy(dst, src.c_str(), src.size()); } else { - memcpy(payload, name.c_str(), maxname); + memcpy(dst, src.c_str(), len); } - return sizeof(Header) + maxname; } -size_t Packet::MakeJoin(const Entity &player, const string &world_name) noexcept { - constexpr size_t maxname = 32; +void Packet::Payload::ReadString(string &dst, size_t off, size_t maxlen) const noexcept { + size_t len = min(maxlen, length - off); + dst.clear(); + dst.reserve(len); + for (size_t i = 0; i < len && data[off + i] != '\0'; ++i) { + dst.push_back(data[off + i]); + } +} - Tag(); - header.type = JOIN; - uint8_t *cursor = &payload[0]; +void Packet::Login::WritePlayerName(const string &name) noexcept { + WriteString(name, 0, 32); +} +void Packet::Login::ReadPlayerName(string &name) const noexcept { + ReadString(name, 0, 32); +} + +void Packet::Join::WritePlayer(const Entity &player) noexcept { // TODO: generate entity IDs - *reinterpret_cast(cursor) = 1; - cursor += 4; + Write(uint32_t(1), 0); + Write(player.ChunkCoords(), 4); + Write(player.Position(), 16); + Write(player.Velocity(), 28); + Write(player.Orientation(), 40); + Write(player.AngularVelocity(), 56); +} - *reinterpret_cast(cursor) = player.ChunkCoords(); - cursor += 12; +void Packet::Join::ReadPlayer(Entity &player) const noexcept { + uint32_t id = 0; + glm::ivec3 chunk_coords(0); + glm::vec3 pos; + glm::vec3 vel; + glm::quat rot; + glm::vec3 ang; - *reinterpret_cast(cursor) = player.Position(); - cursor += 12; - *reinterpret_cast(cursor) = player.Velocity(); - cursor += 12; + Read(id, 0); + Read(chunk_coords, 4); + Read(pos, 16); + Read(vel, 28); + Read(rot, 40); + Read(ang, 56); - *reinterpret_cast(cursor) = player.Orientation(); - cursor += 16; - *reinterpret_cast(cursor) = player.AngularVelocity(); - cursor += 12; + player.Position(chunk_coords, pos); + player.Velocity(vel); + player.Orientation(rot); + player.AngularVelocity(ang); +} - if (world_name.size() < maxname) { - memset(cursor, '\0', maxname); - memcpy(cursor, world_name.c_str(), world_name.size()); - } else { - memcpy(cursor, world_name.c_str(), maxname); - } - cursor += maxname; +void Packet::Join::WriteWorldName(const string &name) noexcept { + WriteString(name, 68, 32); +} - return sizeof(Header) + (cursor - &payload[0]); +void Packet::Join::ReadWorldName(string &name) const noexcept { + ReadString(name, 68, 32); } -size_t Packet::MakePart() noexcept { - Tag(); - header.type = PART; - return sizeof(Header); + +void PacketHandler::Handle(const UDPpacket &udp_pack) { + const Packet &pack = *reinterpret_cast(udp_pack.data); + switch (pack.Type()) { + case Packet::Ping::TYPE: + On(Packet::As(udp_pack)); + break; + case Packet::Login::TYPE: + On(Packet::As(udp_pack)); + break; + case Packet::Join::TYPE: + On(Packet::As(udp_pack)); + break; + case Packet::Part::TYPE: + On(Packet::As(udp_pack)); + break; + default: + // drop unknown or unhandled packets + break; + } } @@ -334,10 +381,10 @@ void Server::HandlePacket(const UDPpacket &udp_pack) { client.Received(udp_pack); switch (pack.header.type) { - case Packet::LOGIN: + case Packet::Login::TYPE: HandleLogin(client, udp_pack); break; - case Packet::PART: + case Packet::Part::TYPE: HandlePart(client, udp_pack); break; default: @@ -384,26 +431,24 @@ void Server::OnDisconnect(Connection &client) { void Server::HandleLogin(Connection &client, const UDPpacket &udp_pack) { - const Packet &pack = *reinterpret_cast(udp_pack.data); - size_t maxlen = min(udp_pack.len - int(sizeof(Packet::Header)), 32); + auto pack = Packet::As(udp_pack); + string name; - name.reserve(maxlen); - for (size_t i = 0; i < maxlen && pack.payload[i] != '\0'; ++i) { - name.push_back(pack.payload[i]); - } + pack.ReadPlayerName(name); Entity *player = world.AddPlayer(name); - Packet &response = *reinterpret_cast(serv_pack.data); if (player) { // success! cout << "accepted login from player \"" << name << '"' << endl; - response.MakeJoin(*player, world.Name()); + auto response = Packet::Make(serv_pack); + response.WritePlayer(*player); + response.WriteWorldName(world.Name()); client.Send(serv_pack, serv_sock); } else { // aw no :( cout << "rejected login from player \"" << name << '"' << endl; - response.MakePart(); + Packet::Make(serv_pack); client.Send(serv_pack, serv_sock); client.Close(); } -- 2.39.2 From 37e056bafe9603981d6bcb205e1472e063c94700 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Fri, 4 Sep 2015 16:38:56 +0200 Subject: [PATCH 09/16] fixed transmission control --- src/client/MasterState.hpp | 9 +- src/client/client.cpp | 22 +++- src/net/Client.hpp | 4 +- src/net/Connection.hpp | 17 +-- ...acketHandler.hpp => ConnectionHandler.hpp} | 10 +- src/net/net.cpp | 103 ++++++++++-------- 6 files changed, 97 insertions(+), 68 deletions(-) rename src/net/{PacketHandler.hpp => ConnectionHandler.hpp} (62%) diff --git a/src/client/MasterState.hpp b/src/client/MasterState.hpp index af2e6aa..6bdb671 100644 --- a/src/client/MasterState.hpp +++ b/src/client/MasterState.hpp @@ -5,7 +5,7 @@ #include "InteractiveState.hpp" #include "../app/State.hpp" #include "../net/Client.hpp" -#include "../net/PacketHandler.hpp" +#include "../net/ConnectionHandler.hpp" #include @@ -20,7 +20,7 @@ class InteractiveState; class MasterState : public State -, public PacketHandler { +, public ConnectionHandler { public: MasterState( @@ -46,6 +46,9 @@ public: void Update(int dt) override; void Render(Viewport &) override; + void OnPacketLost(std::uint16_t) override; + void OnTimeout() override; + void On(const Packet::Join &) override; void On(const Packet::Part &) override; @@ -59,6 +62,8 @@ private: InitialState init_state; + int login_packet; + }; } diff --git a/src/client/client.cpp b/src/client/client.cpp index ac682bb..49ea400 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -120,7 +120,8 @@ MasterState::MasterState( , client_conf(cc) , state() , client(cc) -, init_state(*this) { +, init_state(*this) +, login_packet(-1) { client.GetConnection().SetHandler(this); } @@ -130,7 +131,7 @@ void MasterState::Quit() { void MasterState::OnEnter() { - client.SendLogin(intf_conf.player_name); + login_packet = client.SendLogin(intf_conf.player_name); env.state.Push(&init_state); } @@ -143,10 +144,6 @@ void MasterState::Handle(const SDL_Event &event) { void MasterState::Update(int dt) { client.Handle(); client.Update(dt); - if (client.GetConnection().Closed()) { - Quit(); - // TODO: push disconnected message - } } @@ -155,6 +152,19 @@ void MasterState::Render(Viewport &) { } +void MasterState::OnPacketLost(std::uint16_t id) { + if (id == login_packet) { + login_packet = client.SendLogin(intf_conf.player_name); + } +} + +void MasterState::OnTimeout() { + if (client.GetConnection().Closed()) { + Quit(); + // TODO: push disconnected message + } +} + void MasterState::On(const Packet::Join &pack) { pack.ReadWorldName(world_conf.name); diff --git a/src/net/Client.hpp b/src/net/Client.hpp index 3960e84..bd29c85 100644 --- a/src/net/Client.hpp +++ b/src/net/Client.hpp @@ -30,8 +30,8 @@ public: Connection &GetConnection() noexcept { return conn; } const Connection &GetConnection() const noexcept { return conn; } - void SendPing(); - void SendLogin(const std::string &); + std::uint16_t SendPing(); + std::uint16_t SendLogin(const std::string &); private: void HandlePacket(const UDPpacket &); diff --git a/src/net/Connection.hpp b/src/net/Connection.hpp index 58f315d..ed7a946 100644 --- a/src/net/Connection.hpp +++ b/src/net/Connection.hpp @@ -10,17 +10,17 @@ namespace blank { -class PacketHandler; +class ConnectionHandler; class Connection { public: explicit Connection(const IPaddress &); - void SetHandler(PacketHandler *h) noexcept { handler = h; } + void SetHandler(ConnectionHandler *h) noexcept { handler = h; } void RemoveHandler() noexcept { handler = nullptr; } bool HasHandler() const noexcept { return handler; } - PacketHandler &Handler() noexcept { return *handler; } + ConnectionHandler &Handler() noexcept { return *handler; } const IPaddress &Address() const noexcept { return addr; } @@ -30,13 +30,13 @@ public: bool TimedOut() const noexcept; void Close() noexcept { closed = true; } - bool Closed() const noexcept { return closed || TimedOut(); } + bool Closed() const noexcept { return closed; } void Update(int dt); - void SendPing(UDPpacket &, UDPsocket); + std::uint16_t SendPing(UDPpacket &, UDPsocket); - void Send(UDPpacket &, UDPsocket); + std::uint16_t Send(UDPpacket &, UDPsocket); void Received(const UDPpacket &); private: @@ -44,12 +44,13 @@ private: void FlagRecv() noexcept; private: - PacketHandler *handler; + ConnectionHandler *handler; IPaddress addr; IntervalTimer send_timer; IntervalTimer recv_timer; - Packet::TControl ctrl; + Packet::TControl ctrl_out; + Packet::TControl ctrl_in; bool closed; diff --git a/src/net/PacketHandler.hpp b/src/net/ConnectionHandler.hpp similarity index 62% rename from src/net/PacketHandler.hpp rename to src/net/ConnectionHandler.hpp index 0ea9b57..3eb8f55 100644 --- a/src/net/PacketHandler.hpp +++ b/src/net/ConnectionHandler.hpp @@ -1,5 +1,5 @@ -#ifndef BLANK_NET_PACKETHANDLER_HPP_ -#define BLANK_NET_PACKETHANDLER_HPP_ +#ifndef BLANK_NET_CONNECTIONHANDLER_HPP_ +#define BLANK_NET_CONNECTIONHANDLER_HPP_ #include "Packet.hpp" @@ -8,11 +8,15 @@ namespace blank { -class PacketHandler { +class ConnectionHandler { public: void Handle(const UDPpacket &); + virtual void OnPacketLost(std::uint16_t) { } + + virtual void OnTimeout() { } + private: virtual void On(const Packet::Ping &) { } virtual void On(const Packet::Login &) { } diff --git a/src/net/net.cpp b/src/net/net.cpp index 1be712b..852efd3 100644 --- a/src/net/net.cpp +++ b/src/net/net.cpp @@ -1,8 +1,8 @@ #include "Client.hpp" #include "Connection.hpp" +#include "ConnectionHandler.hpp" #include "io.hpp" #include "Packet.hpp" -#include "PacketHandler.hpp" #include "Server.hpp" #include "../app/init.hpp" @@ -16,6 +16,11 @@ using namespace std; namespace blank { +constexpr size_t Packet::Ping::MAX_LEN; +constexpr size_t Packet::Login::MAX_LEN; +constexpr size_t Packet::Join::MAX_LEN; +constexpr size_t Packet::Part::MAX_LEN; + namespace { UDPsocket client_bind(Uint16 port) { @@ -80,30 +85,29 @@ void Client::HandlePacket(const UDPpacket &udp_pack) { void Client::Update(int dt) { conn.Update(dt); - if (conn.TimedOut()) { - cout << "connection timed out :(" << endl; - } else if (conn.ShouldPing()) { + if (conn.ShouldPing()) { SendPing(); } } -void Client::SendPing() { - conn.SendPing(client_pack, client_sock); +uint16_t Client::SendPing() { + return conn.SendPing(client_pack, client_sock); } -void Client::SendLogin(const string &name) { +uint16_t Client::SendLogin(const string &name) { auto pack = Packet::Make(client_pack); pack.WritePlayerName(name); - conn.Send(client_pack, client_sock); + return conn.Send(client_pack, client_sock); } Connection::Connection(const IPaddress &addr) : handler(nullptr) , addr(addr) -, send_timer(3000) +, send_timer(500) , recv_timer(10000) -, ctrl{ 0, 0xFFFF, 0xFFFF } +, ctrl_out{ 0, 0xFFFF, 0xFFFFFFFF } +, ctrl_in{ 0, 0xFFFF, 0xFFFFFFFF } , closed(false) { send_timer.Start(); recv_timer.Start(); @@ -122,7 +126,7 @@ void Connection::FlagRecv() noexcept { } bool Connection::ShouldPing() const noexcept { - return send_timer.HitOnce(); + return !closed && send_timer.HitOnce(); } bool Connection::TimedOut() const noexcept { @@ -132,15 +136,19 @@ bool Connection::TimedOut() const noexcept { void Connection::Update(int dt) { send_timer.Update(dt); recv_timer.Update(dt); + if (TimedOut()) { + Close(); + if (HasHandler()) { + Handler().OnTimeout(); + } + } } -void Connection::Send(UDPpacket &udp_pack, UDPsocket sock) { +uint16_t Connection::Send(UDPpacket &udp_pack, UDPsocket sock) { Packet &pack = *reinterpret_cast(udp_pack.data); - pack.header.ctrl = ctrl; - ++ctrl.seq; - - cout << "sending " << pack.TypeString() << " to " << Address() << endl; + pack.header.ctrl = ctrl_out; + uint16_t seq = ctrl_out.seq++; udp_pack.address = addr; if (SDLNet_UDP_Send(sock, -1, &udp_pack) == 0) { @@ -148,52 +156,53 @@ void Connection::Send(UDPpacket &udp_pack, UDPsocket sock) { } FlagSend(); + return seq; } void Connection::Received(const UDPpacket &udp_pack) { Packet &pack = *reinterpret_cast(udp_pack.data); - cout << "received " << pack.TypeString() << " from " << Address() << endl; - - int diff = std::int16_t(pack.header.ctrl.seq) - std::int16_t(ctrl.ack); - + // ack to the remote + int16_t diff = int16_t(pack.header.ctrl.seq) - int16_t(ctrl_out.ack); if (diff > 0) { - // incoming more recent than last acked - - // TODO: packets considered lost are detected here - // this should have ones for all of them: - // ~hist & ((1 << dist) - 1) if dist is < 32 - if (diff >= 32) { - // missed more than the last 32 oO - ctrl.hist = 0; - } else { - ctrl.hist >>= diff; - ctrl.hist |= 1 << (32 - diff); - } - } else if (diff < 0) { - // incoming older than acked - if (diff > -32) { - // too late :/ + ctrl_out.hist = 0; } else { - ctrl.hist |= 1 << (32 + diff); + ctrl_out.hist <<= diff; + ctrl_out.hist |= 1 << (diff - 1); } - } else { - // incoming the same as last acked oO + } else if (diff < 0 && diff >= -32) { + ctrl_out.hist |= 1 << (-diff - 1); } + ctrl_out.ack = pack.header.ctrl.seq; + FlagRecv(); - ctrl.ack = pack.header.ctrl.seq; - - if (HasHandler()) { - Handler().Handle(udp_pack); + if (!HasHandler()) { + return; } - FlagRecv(); + Packet::TControl ctrl_new = pack.header.ctrl; + Handler().Handle(udp_pack); + + if (diff > 0) { + // if the packet holds more recent information + // check if remote failed to ack one of our packets + diff = int16_t(ctrl_new.ack) - int16_t(ctrl_in.ack); + // should always be true, but you never know… + if (diff > 0) { + for (int i = 0; i < diff; ++i) { + if (i > 32 || (i < 32 && (ctrl_in.hist & (1 << (31 - i))) == 0)) { + Handler().OnPacketLost(ctrl_in.ack - 32 + i); + } + } + } + ctrl_in = ctrl_new; + } } -void Connection::SendPing(UDPpacket &udp_pack, UDPsocket sock) { +uint16_t Connection::SendPing(UDPpacket &udp_pack, UDPsocket sock) { Packet::Make(udp_pack); - Send(udp_pack, sock); + return Send(udp_pack, sock); } @@ -312,7 +321,7 @@ void Packet::Join::ReadWorldName(string &name) const noexcept { } -void PacketHandler::Handle(const UDPpacket &udp_pack) { +void ConnectionHandler::Handle(const UDPpacket &udp_pack) { const Packet &pack = *reinterpret_cast(udp_pack.data); switch (pack.Type()) { case Packet::Ping::TYPE: -- 2.39.2 From 2b31a783f1bac06c7f6d7adbe5e13e4148c7a815 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Fri, 4 Sep 2015 16:44:19 +0200 Subject: [PATCH 10/16] fix stupid bug in packet builder --- src/net/Packet.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/net/Packet.hpp b/src/net/Packet.hpp index bd9dd86..fa35189 100644 --- a/src/net/Packet.hpp +++ b/src/net/Packet.hpp @@ -105,7 +105,7 @@ struct Packet { pack.Tag(); pack.Type(PayloadType::TYPE); - udp_pack.len = sizeof(Header) + PayloadType::TYPE; + udp_pack.len = sizeof(Header) + PayloadType::MAX_LEN; PayloadType result; result.length = PayloadType::MAX_LEN; -- 2.39.2 From 4e3d9c23940c4511623b5bf328cbbe42641c4b30 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Sat, 5 Sep 2015 00:27:15 +0200 Subject: [PATCH 11/16] give unique IDs to entities please don't modify from outside the world :P --- src/app/WorldState.cpp | 2 +- src/client/InteractiveState.hpp | 2 +- src/client/client.cpp | 15 ++++++-- src/net/Packet.hpp | 1 + src/net/net.cpp | 9 ++--- src/ui/Interface.hpp | 3 +- src/ui/ui.cpp | 5 +-- src/world/Entity.cpp | 1 + src/world/Entity.hpp | 5 +++ src/world/World.cpp | 64 +++++++++++++++++++++++++++++++++ src/world/World.hpp | 10 +++++- 11 files changed, 104 insertions(+), 13 deletions(-) diff --git a/src/app/WorldState.cpp b/src/app/WorldState.cpp index fabd332..db8c963 100644 --- a/src/app/WorldState.cpp +++ b/src/app/WorldState.cpp @@ -20,7 +20,7 @@ WorldState::WorldState( , world(block_types, wc, save) , chunk_renderer(world, wc.load.load_dist) , spawner(world, wc.gen.seed) -, interface(ic, env, world) +, interface(ic, env, world, *world.AddPlayer(ic.player_name)) , preload(env, world.Loader(), chunk_renderer) , unload(env, world.Loader()) { TextureIndex tex_index; diff --git a/src/client/InteractiveState.hpp b/src/client/InteractiveState.hpp index e679eb2..65c92d0 100644 --- a/src/client/InteractiveState.hpp +++ b/src/client/InteractiveState.hpp @@ -21,7 +21,7 @@ class InteractiveState : public State { public: - explicit InteractiveState(MasterState &); + explicit InteractiveState(MasterState &, std::uint32_t player_id); World &GetWorld() noexcept { return world; } Interface &GetInterface() noexcept { return interface; } diff --git a/src/client/client.cpp b/src/client/client.cpp index 49ea400..8dd575a 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -38,13 +38,19 @@ void InitialState::Render(Viewport &viewport) { } -InteractiveState::InteractiveState(MasterState &master) +// TODO: this clutter is a giant mess +InteractiveState::InteractiveState(MasterState &master, uint32_t player_id) : master(master) , block_types() , save(master.GetEnv().config.GetWorldPath(master.GetWorldConf().name, master.GetClientConf().host)) , world(block_types, master.GetWorldConf(), save) , chunk_renderer(world, master.GetWorldConf().load.load_dist) -, interface(master.GetInterfaceConf(), master.GetEnv(), world) { +, interface( + master.GetInterfaceConf(), + master.GetEnv(), + world, + *world.AddPlayer(master.GetInterfaceConf().player_name, player_id) +) { TextureIndex tex_index; master.GetEnv().loader.LoadBlockTypes("default", block_types, tex_index); chunk_renderer.LoadTextures(master.GetEnv().loader, tex_index); @@ -175,7 +181,10 @@ void MasterState::On(const Packet::Join &pack) { // joining game std::cout << "joined game" << std::endl; } - state.reset(new InteractiveState(*this)); + + uint32_t player_id; + pack.ReadPlayerID(player_id); + state.reset(new InteractiveState(*this, player_id)); pack.ReadPlayer(state->GetInterface().Player()); diff --git a/src/net/Packet.hpp b/src/net/Packet.hpp index fa35189..af2f872 100644 --- a/src/net/Packet.hpp +++ b/src/net/Packet.hpp @@ -72,6 +72,7 @@ struct Packet { static constexpr std::size_t MAX_LEN = 100; void WritePlayer(const Entity &) noexcept; + void ReadPlayerID(std::uint32_t &) const noexcept; void ReadPlayer(Entity &) const noexcept; void WriteWorldName(const std::string &) noexcept; void ReadWorldName(std::string &) const noexcept; diff --git a/src/net/net.cpp b/src/net/net.cpp index 852efd3..4d36412 100644 --- a/src/net/net.cpp +++ b/src/net/net.cpp @@ -282,8 +282,7 @@ void Packet::Login::ReadPlayerName(string &name) const noexcept { } void Packet::Join::WritePlayer(const Entity &player) noexcept { - // TODO: generate entity IDs - Write(uint32_t(1), 0); + Write(player.ID(), 0); Write(player.ChunkCoords(), 4); Write(player.Position(), 16); Write(player.Velocity(), 28); @@ -291,15 +290,17 @@ void Packet::Join::WritePlayer(const Entity &player) noexcept { Write(player.AngularVelocity(), 56); } +void Packet::Join::ReadPlayerID(uint32_t &id) const noexcept { + Read(id, 0); +} + void Packet::Join::ReadPlayer(Entity &player) const noexcept { - uint32_t id = 0; glm::ivec3 chunk_coords(0); glm::vec3 pos; glm::vec3 vel; glm::quat rot; glm::vec3 ang; - Read(id, 0); Read(chunk_coords, 4); Read(pos, 16); Read(vel, 28); diff --git a/src/ui/Interface.hpp b/src/ui/Interface.hpp index aab8b15..7c9a91e 100644 --- a/src/ui/Interface.hpp +++ b/src/ui/Interface.hpp @@ -20,6 +20,7 @@ namespace blank { +class Entity; class Environment; class Viewport; class World; @@ -40,7 +41,7 @@ public: bool visual_disabled = false; }; - Interface(const Config &, Environment &, World &); + Interface(const Config &, Environment &, World &, Entity &); Entity &Player() noexcept { return ctrl.Controlled(); } const Entity &Player() const noexcept { return ctrl.Controlled(); } diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index d8459e5..2ec4d88 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -102,11 +102,12 @@ void HUD::Render(Viewport &viewport) noexcept { Interface::Interface( const Config &config, Environment &env, - World &world) + World &world, + Entity &player) : env(env) , world(world) // let's assume this succeeds and hope for the best for now -, ctrl(*world.AddPlayer(config.player_name)) +, ctrl(player) , hud(world.BlockTypes(), env.assets.small_ui_font) , aim{{ 0, 0, 0 }, { 0, 0, -1 }} , aim_world() diff --git a/src/world/Entity.cpp b/src/world/Entity.cpp index f116153..b909603 100644 --- a/src/world/Entity.cpp +++ b/src/world/Entity.cpp @@ -16,6 +16,7 @@ namespace blank { Entity::Entity() noexcept : model() +, id(-1) , name("anonymous") , bounds() , velocity(0, 0, 0) diff --git a/src/world/Entity.hpp b/src/world/Entity.hpp index a381142..c0d9a0b 100644 --- a/src/world/Entity.hpp +++ b/src/world/Entity.hpp @@ -5,6 +5,7 @@ #include "../model/CompositeInstance.hpp" #include "../model/geometry.hpp" +#include #include #include #include @@ -23,6 +24,9 @@ public: CompositeInstance &GetModel() noexcept { return model; } const CompositeInstance &GetModel() const noexcept { return model; } + std::uint32_t ID() const noexcept { return id; } + void ID(std::uint32_t i) noexcept { id = i; } + const std::string &Name() const noexcept { return name; } void Name(const std::string &n) { name = n; } @@ -77,6 +81,7 @@ public: private: CompositeInstance model; + std::uint32_t id; std::string name; AABB bounds; diff --git a/src/world/World.cpp b/src/world/World.cpp index 06ccb62..6ad30ff 100644 --- a/src/world/World.cpp +++ b/src/world/World.cpp @@ -46,6 +46,70 @@ Entity *World::AddPlayer(const std::string &name) { return &player; } +Entity *World::AddPlayer(const std::string &name, std::uint32_t id) { + for (Entity *e : players) { + if (e->Name() == name) { + return nullptr; + } + } + Entity *player = AddEntity(id); + if (!player) { + return nullptr; + } + player->Name(name); + // TODO: load from save file here + player->Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } }); + player->WorldCollidable(true); + player->Position(config.spawn); + players.push_back(player); + chunks.QueueSurrounding(player->ChunkCoords()); + return player; +} + +Entity &World::AddEntity() { + if (entities.empty()) { + entities.emplace_back(); + entities.back().ID(1); + return entities.back(); + } + if (entities.back().ID() < std::numeric_limits::max()) { + std::uint32_t id = entities.back().ID() + 1; + entities.emplace_back(); + entities.back().ID(id); + return entities.back(); + } + std::uint32_t id = 1; + auto position = entities.begin(); + auto end = entities.end(); + while (position != end && position->ID() == id) { + ++id; + ++position; + } + auto entity = entities.emplace(position); + entity->ID(id); + return *entity; +} + +Entity *World::AddEntity(std::uint32_t id) { + if (entities.empty() || entities.back().ID() < id) { + entities.emplace_back(); + entities.back().ID(id); + return &entities.back(); + } + + auto position = entities.begin(); + auto end = entities.end(); + while (position != end && position->ID() < id) { + ++position; + } + if (position != end && position->ID() == id) { + return nullptr; + } + auto entity = entities.emplace(position); + entity->ID(id); + return &*entity; +} + namespace { diff --git a/src/world/World.hpp b/src/world/World.hpp index 7d2c292..9ae0faa 100644 --- a/src/world/World.hpp +++ b/src/world/World.hpp @@ -5,6 +5,7 @@ #include "Entity.hpp" #include "Generator.hpp" +#include #include #include #include @@ -70,7 +71,14 @@ public: /// add player with given name /// returns nullptr if the name is already taken Entity *AddPlayer(const std::string &name); - Entity &AddEntity() { entities.emplace_back(); return entities.back(); } + /// add player with given name and ID + /// returns nullptr if the name or ID is already taken + Entity *AddPlayer(const std::string &name, std::uint32_t id); + /// add an entity with an autogenerated ID + Entity &AddEntity(); + /// add entity with given ID + /// returns nullptr if the ID is already taken + Entity *AddEntity(std::uint32_t id); const std::vector &Players() const noexcept { return players; } const std::list &Entities() const noexcept { return entities; } -- 2.39.2 From 1812e1a29378526a59a346caa2348df3e7522049 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Sat, 5 Sep 2015 14:56:40 +0200 Subject: [PATCH 12/16] documented packet structure --- doc/protocol | 20 ++++++++++++++++++++ src/net/Packet.hpp | 3 +++ 2 files changed, 23 insertions(+) diff --git a/doc/protocol b/doc/protocol index 63055a2..a20d8d6 100644 --- a/doc/protocol +++ b/doc/protocol @@ -1,3 +1,23 @@ +Structure +========= + +Offset Size Type Description + 0 4 data protocol tag, must be 0xFB1AB1AF + 4 2 uint sequence number + 6 2 uint sequence ack + 8 4 data bitfield with previous acks +12 1 uint type code for the payload +13 3 none padding, reserved for future use +16 0-484 data payload, contents and length vary, + mostly depending on the type code + +all multibyte values are in LE +the current code does no conversion, so only works on machines +with native LE (or BE if the server and all clients are on that, +but that's by accident and will break if conversion code is ever +added) + + Packets ======= diff --git a/src/net/Packet.hpp b/src/net/Packet.hpp index af2f872..5e9751c 100644 --- a/src/net/Packet.hpp +++ b/src/net/Packet.hpp @@ -27,6 +27,9 @@ struct Packet { std::uint32_t tag; TControl ctrl; std::uint8_t type; + std::uint8_t reserved1; + std::uint8_t reserved2; + std::uint8_t reserved3; } header; static constexpr std::size_t MAX_PAYLOAD_LEN = 500 - sizeof(Header); -- 2.39.2 From 416f96590d7e09433549fb4cc35c21b1b437ac4c Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Sat, 5 Sep 2015 14:56:49 +0200 Subject: [PATCH 13/16] adjust player index if entity is removed --- src/world/World.cpp | 11 ++++++++++- src/world/World.hpp | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/world/World.cpp b/src/world/World.cpp index 6ad30ff..7b90954 100644 --- a/src/world/World.cpp +++ b/src/world/World.cpp @@ -227,7 +227,7 @@ void World::Update(int dt) { } for (auto iter = entities.begin(), end = entities.end(); iter != end;) { if (iter->CanRemove()) { - iter = entities.erase(iter); + iter = RemoveEntity(iter); } else { ++iter; } @@ -269,6 +269,15 @@ void World::Resolve(Entity &e, std::vector &col) { e.Move(final_disp); } +World::EntityHandle World::RemoveEntity(EntityHandle &eh) { + // check for player + auto player = std::find(players.begin(), players.end(), &*eh); + if (player != players.end()) { + players.erase(player); + } + return entities.erase(eh); +} + void World::Render(Viewport &viewport) { DirectionalLighting &entity_prog = viewport.EntityProgram(); diff --git a/src/world/World.hpp b/src/world/World.hpp index 9ae0faa..9739e21 100644 --- a/src/world/World.hpp +++ b/src/world/World.hpp @@ -87,6 +87,10 @@ public: void Render(Viewport &); +private: + using EntityHandle = std::list::iterator; + EntityHandle RemoveEntity(EntityHandle &); + private: Config config; -- 2.39.2 From 99345b497912db65204d48348140fc774dcb6989 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Sat, 5 Sep 2015 15:45:11 +0200 Subject: [PATCH 14/16] make server aware connected clients' player entity --- src/net/ClientConnection.hpp | 44 ++++++++++++ src/net/Server.hpp | 17 +++-- src/net/net.cpp | 132 ++++++++++++++++++----------------- 3 files changed, 121 insertions(+), 72 deletions(-) create mode 100644 src/net/ClientConnection.hpp diff --git a/src/net/ClientConnection.hpp b/src/net/ClientConnection.hpp new file mode 100644 index 0000000..acbee6e --- /dev/null +++ b/src/net/ClientConnection.hpp @@ -0,0 +1,44 @@ +#ifndef BLANK_NET_CLIENTCONNECTION_HPP_ +#define BLANK_NET_CLIENTCONNECTION_HPP_ + +#include "Connection.hpp" +#include "ConnectionHandler.hpp" + +#include + + +namespace blank { + +class Entity; +class Server; + +class ClientConnection +: public ConnectionHandler { + +public: + explicit ClientConnection(Server &, const IPaddress &); + ~ClientConnection(); + + bool Matches(const IPaddress &addr) const noexcept { return conn.Matches(addr); } + + void Update(int dt); + + Connection &GetConnection() noexcept { return conn; } + bool Disconnected() const noexcept { return conn.Closed(); } + + void AttachPlayer(Entity &); + void DetachPlayer(); + + void On(const Packet::Login &) override; + void On(const Packet::Part &) override; + +private: + Server &server; + Connection conn; + Entity *player; + +}; + +} + +#endif diff --git a/src/net/Server.hpp b/src/net/Server.hpp index a95cd37..a6d509b 100644 --- a/src/net/Server.hpp +++ b/src/net/Server.hpp @@ -7,7 +7,7 @@ namespace blank { -class Connection; +class ClientConnection; class World; class Server { @@ -25,21 +25,20 @@ public: void Update(int dt); -private: - void HandlePacket(const UDPpacket &); + UDPsocket &GetSocket() noexcept { return serv_sock; } + UDPpacket &GetPacket() noexcept { return serv_pack; } - Connection &GetClient(const IPaddress &); + World &GetWorld() noexcept { return world; } - void OnConnect(Connection &); - void OnDisconnect(Connection &); +private: + void HandlePacket(const UDPpacket &); - void HandleLogin(Connection &client, const UDPpacket &); - void HandlePart(Connection &client, const UDPpacket &); + ClientConnection &GetClient(const IPaddress &); private: UDPsocket serv_sock; UDPpacket serv_pack; - std::list clients; + std::list clients; World &world; diff --git a/src/net/net.cpp b/src/net/net.cpp index 4d36412..7f12e63 100644 --- a/src/net/net.cpp +++ b/src/net/net.cpp @@ -1,4 +1,5 @@ #include "Client.hpp" +#include "ClientConnection.hpp" #include "Connection.hpp" #include "ConnectionHandler.hpp" #include "io.hpp" @@ -101,6 +102,67 @@ uint16_t Client::SendLogin(const string &name) { } +ClientConnection::ClientConnection(Server &server, const IPaddress &addr) +: server(server) +, conn(addr) +, player(nullptr) { + conn.SetHandler(this); +} + +ClientConnection::~ClientConnection() { + DetachPlayer(); +} + +void ClientConnection::Update(int dt) { + conn.Update(dt); + if (Disconnected()) { + cout << "disconnect from " << conn.Address() << endl; + } else if (conn.ShouldPing()) { + conn.SendPing(server.GetPacket(), server.GetSocket()); + } +} + +void ClientConnection::AttachPlayer(Entity &new_player) { + DetachPlayer(); + player = &new_player; + player->Ref(); +} + +void ClientConnection::DetachPlayer() { + if (!player) return; + player->Kill(); + player->UnRef(); + player = nullptr; +} + +void ClientConnection::On(const Packet::Login &pack) { + string name; + pack.ReadPlayerName(name); + + Entity *new_player = server.GetWorld().AddPlayer(name); + + if (new_player) { + // success! + AttachPlayer(*new_player); + cout << "accepted login from player \"" << name << '"' << endl; + auto response = Packet::Make(server.GetPacket()); + response.WritePlayer(*new_player); + response.WriteWorldName(server.GetWorld().Name()); + conn.Send(server.GetPacket(), server.GetSocket()); + } else { + // aw no :( + cout << "rejected login from player \"" << name << '"' << endl; + Packet::Make(server.GetPacket()); + conn.Send(server.GetPacket(), server.GetSocket()); + conn.Close(); + } +} + +void ClientConnection::On(const Packet::Part &) { + conn.Close(); +} + + Connection::Connection(const IPaddress &addr) : handler(nullptr) , addr(addr) @@ -387,85 +449,29 @@ void Server::HandlePacket(const UDPpacket &udp_pack) { return; } - Connection &client = GetClient(udp_pack.address); - client.Received(udp_pack); - - switch (pack.header.type) { - case Packet::Login::TYPE: - HandleLogin(client, udp_pack); - break; - case Packet::Part::TYPE: - HandlePart(client, udp_pack); - break; - default: - // just drop packets of unknown or unhandled type - break; - } + ClientConnection &client = GetClient(udp_pack.address); + client.GetConnection().Received(udp_pack); } -Connection &Server::GetClient(const IPaddress &addr) { - for (Connection &client : clients) { +ClientConnection &Server::GetClient(const IPaddress &addr) { + for (ClientConnection &client : clients) { if (client.Matches(addr)) { return client; } } - clients.emplace_back(addr); - OnConnect(clients.back()); + clients.emplace_back(*this, addr); return clients.back(); } -void Server::OnConnect(Connection &client) { - cout << "new connection from " << client.Address() << endl; - // tell it we're alive - client.SendPing(serv_pack, serv_sock); -} - void Server::Update(int dt) { - for (list::iterator client(clients.begin()), end(clients.end()); client != end;) { + for (list::iterator client(clients.begin()), end(clients.end()); client != end;) { client->Update(dt); - if (client->Closed()) { - OnDisconnect(*client); + if (client->Disconnected()) { client = clients.erase(client); } else { - if (client->ShouldPing()) { - client->SendPing(serv_pack, serv_sock); - } ++client; } } } -void Server::OnDisconnect(Connection &client) { - cout << "connection timeout from " << client.Address() << endl; -} - - -void Server::HandleLogin(Connection &client, const UDPpacket &udp_pack) { - auto pack = Packet::As(udp_pack); - - string name; - pack.ReadPlayerName(name); - - Entity *player = world.AddPlayer(name); - - if (player) { - // success! - cout << "accepted login from player \"" << name << '"' << endl; - auto response = Packet::Make(serv_pack); - response.WritePlayer(*player); - response.WriteWorldName(world.Name()); - client.Send(serv_pack, serv_sock); - } else { - // aw no :( - cout << "rejected login from player \"" << name << '"' << endl; - Packet::Make(serv_pack); - client.Send(serv_pack, serv_sock); - client.Close(); - } -} - -void Server::HandlePart(Connection &client, const UDPpacket &udp_pack) { - client.Close(); -} - } -- 2.39.2 From c6ca9d21e45af5ea7caeec722a9b59fdf3aa3b24 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Sat, 5 Sep 2015 16:16:45 +0200 Subject: [PATCH 15/16] send player updates from client to server --- doc/protocol | 16 +++++++++++++ src/client/client.cpp | 2 ++ src/net/Client.hpp | 1 + src/net/ClientConnection.hpp | 3 +++ src/net/ConnectionHandler.hpp | 1 + src/net/Packet.hpp | 8 +++++++ src/net/net.cpp | 45 +++++++++++++++++++++++++++++++++++ 7 files changed, 76 insertions(+) diff --git a/doc/protocol b/doc/protocol index a20d8d6..43c14da 100644 --- a/doc/protocol +++ b/doc/protocol @@ -30,6 +30,7 @@ a while. Code: 0 Payload: none +Length: 0 Login @@ -43,6 +44,7 @@ Code: 1 Payload: 0 player name, max 32 byte UTF-8 string, shorter names should be zero terminated +Length: 0-32 Join @@ -58,6 +60,7 @@ Payload: 16 pos/vel/rot/ang of the player, 13x 32bit float 68 name of the world the server's currently running max 32 byte UTF-8 string +Length: 68-100 Part @@ -69,3 +72,16 @@ Optionally sent by the client on disconnect. Code: 3 Payload: none +Length: 0 + + +Player Update +------------- + +Sent by clients to notify the server of their changes to the player. + +Code: 4 +Payload: + 0 chunk coords of the player, 3x 32bit signed int + 12 pos/vel/rot/ang of the player, 13x 32bit float +Length: 64 diff --git a/src/client/client.cpp b/src/client/client.cpp index 8dd575a..9c87549 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -99,6 +99,8 @@ void InteractiveState::Update(int dt) { chunk_renderer.Rebase(interface.Player().ChunkCoords()); chunk_renderer.Update(dt); + master.GetClient().SendPlayerUpdate(interface.Player()); + glm::mat4 trans = interface.Player().Transform(interface.Player().ChunkCoords()); glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f)); glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)); diff --git a/src/net/Client.hpp b/src/net/Client.hpp index bd29c85..5c0a9c2 100644 --- a/src/net/Client.hpp +++ b/src/net/Client.hpp @@ -32,6 +32,7 @@ public: std::uint16_t SendPing(); std::uint16_t SendLogin(const std::string &); + std::uint16_t SendPlayerUpdate(const Entity &); private: void HandlePacket(const UDPpacket &); diff --git a/src/net/ClientConnection.hpp b/src/net/ClientConnection.hpp index acbee6e..8c5306c 100644 --- a/src/net/ClientConnection.hpp +++ b/src/net/ClientConnection.hpp @@ -28,9 +28,12 @@ public: void AttachPlayer(Entity &); void DetachPlayer(); + bool HasPlayer() const noexcept { return player; } + Entity &Player() noexcept { return *player; } void On(const Packet::Login &) override; void On(const Packet::Part &) override; + void On(const Packet::PlayerUpdate &) override; private: Server &server; diff --git a/src/net/ConnectionHandler.hpp b/src/net/ConnectionHandler.hpp index 3eb8f55..29595e5 100644 --- a/src/net/ConnectionHandler.hpp +++ b/src/net/ConnectionHandler.hpp @@ -22,6 +22,7 @@ private: virtual void On(const Packet::Login &) { } virtual void On(const Packet::Join &) { } virtual void On(const Packet::Part &) { } + virtual void On(const Packet::PlayerUpdate &) { } }; diff --git a/src/net/Packet.hpp b/src/net/Packet.hpp index 5e9751c..0dab949 100644 --- a/src/net/Packet.hpp +++ b/src/net/Packet.hpp @@ -86,6 +86,14 @@ struct Packet { static constexpr std::size_t MAX_LEN = 0; }; + struct PlayerUpdate : public Payload { + static constexpr std::uint8_t TYPE = 4; + static constexpr std::size_t MAX_LEN = 64; + + void WritePlayer(const Entity &) noexcept; + void ReadPlayer(Entity &) const noexcept; + }; + template PayloadType As() { diff --git a/src/net/net.cpp b/src/net/net.cpp index 7f12e63..837a7fb 100644 --- a/src/net/net.cpp +++ b/src/net/net.cpp @@ -11,6 +11,7 @@ #include #include +#include using namespace std; @@ -21,6 +22,7 @@ constexpr size_t Packet::Ping::MAX_LEN; constexpr size_t Packet::Login::MAX_LEN; constexpr size_t Packet::Join::MAX_LEN; constexpr size_t Packet::Part::MAX_LEN; +constexpr size_t Packet::PlayerUpdate::MAX_LEN; namespace { @@ -101,6 +103,12 @@ uint16_t Client::SendLogin(const string &name) { return conn.Send(client_pack, client_sock); } +uint16_t Client::SendPlayerUpdate(const Entity &player) { + auto pack = Packet::Make(client_pack); + pack.WritePlayer(player); + return conn.Send(client_pack, client_sock); +} + ClientConnection::ClientConnection(Server &server, const IPaddress &addr) : server(server) @@ -162,6 +170,11 @@ void ClientConnection::On(const Packet::Part &) { conn.Close(); } +void ClientConnection::On(const Packet::PlayerUpdate &pack) { + if (!HasPlayer()) return; + pack.ReadPlayer(Player()); +} + Connection::Connection(const IPaddress &addr) : handler(nullptr) @@ -291,6 +304,8 @@ const char *Packet::Type2String(uint8_t t) noexcept { return "Join"; case Part::TYPE: return "Part"; + case PlayerUpdate::TYPE: + return "PlayerUpdate"; default: return "Unknown"; } @@ -383,6 +398,33 @@ void Packet::Join::ReadWorldName(string &name) const noexcept { ReadString(name, 68, 32); } +void Packet::PlayerUpdate::WritePlayer(const Entity &player) noexcept { + Write(player.ChunkCoords(), 0); + Write(player.Position(), 12); + Write(player.Velocity(), 24); + Write(player.Orientation(), 36); + Write(player.AngularVelocity(), 52); +} + +void Packet::PlayerUpdate::ReadPlayer(Entity &player) const noexcept { + glm::ivec3 chunk_coords(0); + glm::vec3 pos; + glm::vec3 vel; + glm::quat rot; + glm::vec3 ang; + + Read(chunk_coords, 0); + Read(pos, 12); + Read(vel, 24); + Read(rot, 36); + Read(ang, 52); + + player.Position(chunk_coords, pos); + player.Velocity(vel); + player.Orientation(rot); + player.AngularVelocity(ang); +} + void ConnectionHandler::Handle(const UDPpacket &udp_pack) { const Packet &pack = *reinterpret_cast(udp_pack.data); @@ -399,6 +441,9 @@ void ConnectionHandler::Handle(const UDPpacket &udp_pack) { case Packet::Part::TYPE: On(Packet::As(udp_pack)); break; + case Packet::PlayerUpdate::TYPE: + On(Packet::As(udp_pack)); + break; default: // drop unknown or unhandled packets break; -- 2.39.2 From b5a83cd3df5083ed0cccfe4876143e788b3836f9 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Mon, 7 Sep 2015 16:37:43 +0200 Subject: [PATCH 16/16] also tell connection handlers about ack'd packets --- src/net/ConnectionHandler.hpp | 3 +++ src/net/Packet.hpp | 5 +++++ src/net/net.cpp | 13 +++++++++++++ 3 files changed, 21 insertions(+) diff --git a/src/net/ConnectionHandler.hpp b/src/net/ConnectionHandler.hpp index 29595e5..ab9761a 100644 --- a/src/net/ConnectionHandler.hpp +++ b/src/net/ConnectionHandler.hpp @@ -13,6 +13,9 @@ class ConnectionHandler { public: void Handle(const UDPpacket &); + // called as soon as the remote end ack'd given packet + virtual void OnPacketReceived(std::uint16_t) { } + // called if the remote end probably didn't get given packet virtual void OnPacketLost(std::uint16_t) { } virtual void OnTimeout() { } diff --git a/src/net/Packet.hpp b/src/net/Packet.hpp index 0dab949..4ff45df 100644 --- a/src/net/Packet.hpp +++ b/src/net/Packet.hpp @@ -21,6 +21,11 @@ struct Packet { std::uint16_t seq; std::uint16_t ack; std::uint32_t hist; + + // true if this contains an ack for given (remote) seq + bool Acks(std::uint16_t) const noexcept; + std::uint16_t AckBegin() const noexcept { return ack; } + std::uint16_t AckEnd() const noexcept { return ack + std::uint16_t(33); } }; struct Header { diff --git a/src/net/net.cpp b/src/net/net.cpp index 837a7fb..1876157 100644 --- a/src/net/net.cpp +++ b/src/net/net.cpp @@ -271,10 +271,23 @@ void Connection::Received(const UDPpacket &udp_pack) { } } } + // check for newly ack'd packets + for (uint16_t s = ctrl_new.AckBegin(); s != ctrl_new.AckEnd(); ++s) { + if (ctrl_new.Acks(s) && !ctrl_in.Acks(s)) { + Handler().OnPacketReceived(s); + } + } ctrl_in = ctrl_new; } } +bool Packet::TControl::Acks(uint16_t s) const noexcept { + int16_t diff = int16_t(ack) - int16_t(s); + if (diff == 0) return true; + if (diff < 0 || diff > 32) return false; + return (hist & (1 << (diff - 1))) != 0; +} + uint16_t Connection::SendPing(UDPpacket &udp_pack, UDPsocket sock) { Packet::Make(udp_pack); return Send(udp_pack, sock); -- 2.39.2