]> git.localhorst.tv Git - blank.git/commitdiff
first draft for client/server architecture
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 2 Sep 2015 15:27:01 +0000 (17:27 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 2 Sep 2015 15:34:26 +0000 (17:34 +0200)
27 files changed:
Makefile
building
running
src/app/Application.hpp
src/app/Assets.hpp
src/app/ClientState.cpp [new file with mode: 0644]
src/app/ClientState.hpp [new file with mode: 0644]
src/app/Environment.hpp
src/app/Runtime.hpp
src/app/ServerState.cpp [new file with mode: 0644]
src/app/ServerState.hpp [new file with mode: 0644]
src/app/State.hpp
src/app/StateControl.hpp
src/app/WorldState.cpp
src/app/app.cpp
src/app/init.cpp
src/app/init.hpp
src/app/runtime.cpp
src/net/Client.hpp [new file with mode: 0644]
src/net/Connection.hpp [new file with mode: 0644]
src/net/Packet.hpp [new file with mode: 0644]
src/net/Server.hpp [new file with mode: 0644]
src/net/io.hpp [new file with mode: 0644]
src/net/net.cpp [new file with mode: 0644]
src/ui/ui.cpp
src/world/ChunkRenderer.hpp
src/world/render.cpp

index 7bafccd6a6e74e2c436e4b0f218736d30237219b..e6e24d9d2845caea6a24b673c9752d48bbeeda00 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 CXX = g++ --std=c++11
 LDXX = g++
 
 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))
 
 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/
 
 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
 
 gdb: $(ASSET_DEP) blank.debug
        gdb ./blank.debug
 
index bc22d9b55423ad9a8db25396434f5b8cd639b34c..ef837750adae5fea4f624e5d7357822fc4fa47ce 100644 (file)
--- a/building
+++ b/building
@@ -1,11 +1,11 @@
 Dependencies
 ============
 
 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
 
 
        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/
 
 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 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
 
 test:
        build and run unittests
diff --git a/running b/running
index 8638f4338144b0b9c10e9326fafa1f87873d296d..7c089ff27db902799bb68b1024f1f8ac5bfb0091 100644 (file)
--- a/running
+++ b/running
@@ -34,6 +34,15 @@ Application
 --no-vsync
        disable vsync
 
 --no-vsync
        disable vsync
 
+--standalone
+       run as standalone (the default)
+
+--client
+       run as client
+
+--server
+       run as server
+
 Interface
 ---------
 
 Interface
 ---------
 
@@ -51,6 +60,15 @@ Interface
        the audio device and sounds will still be allocated
        it just stops the interface from queueing buffers
 
        the audio device and sounds will still be allocated
        it just stops the interface from queueing buffers
 
+Network
+-------
+
+--host <hostname>
+       hostname to connect to in client mode
+
+--port <number>
+       port number to connection to (client) or listen on (server)
+
 World
 -----
 
 World
 -----
 
index bae8435a0084811151300e825b9c5fec402f35d8..359599ef73750de04c0ee3de16c489690114a005 100644 (file)
@@ -8,22 +8,27 @@
 namespace blank {
 
 class Environment;
 namespace blank {
 
 class Environment;
+class HeadlessEnvironment;
 class State;
 class Window;
 
 class State;
 class Window;
 
-class Application {
+class HeadlessApplication {
 
 public:
 
 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 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);
 
        /// 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);
 
        /// 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<State *> 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 &);
        /// 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();
 
        /// 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;
 private:
        Environment &env;
-       std::stack<State *> states;
 
 };
 
 
 };
 
index 65c72fe04d73ad30a1fcf9d215244ad3dfe4ec66..d1a4ae710d0cc832fbcb155b2756eebcc9718cba 100644 (file)
@@ -14,10 +14,10 @@ class Sound;
 class Texture;
 class TextureIndex;
 
 class Texture;
 class TextureIndex;
 
-class Assets {
+class AssetLoader {
 
 public:
 
 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;
 
        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;
 
        std::string textures;
        std::string data;
 
-public:
-       // common assets shared by may states
+};
+
+struct Assets {
+
        Font large_ui_font;
        Font small_ui_font;
 
        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 (file)
index 0000000..c371235
--- /dev/null
@@ -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 (file)
index 0000000..f54e56f
--- /dev/null
@@ -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
index 4c935d815e2f4a13483452e791a203dcfce31db7..95813fc12dd7ad43f5e6352924939a80c26040e2 100644 (file)
@@ -15,20 +15,33 @@ namespace blank {
 
 class Window;
 
 
 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;
 
 
        Audio audio;
        Viewport viewport;
        Window &window;
 
-       Assets assets;
        Keymap keymap;
        Keymap keymap;
-       FrameCounter counter;
-
-       StateControl state;
 
 
 
 
-       explicit Environment(Window &win, const std::string &asset_path);
+       Environment(Window &win, const std::string &asset_path);
 
 };
 
 
 };
 
index 81c015c3668456360b1c84dc77587ba026848b1a..ee438537a345ef43e0f884f24ca799588b15ba4a 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef BLANK_RUNTIME_HPP_
 #define BLANK_RUNTIME_HPP_
 
 #ifndef BLANK_RUNTIME_HPP_
 #define BLANK_RUNTIME_HPP_
 
+#include "../net/Client.hpp"
+#include "../net/Server.hpp"
 #include "../ui/Interface.hpp"
 #include "../world/World.hpp"
 
 #include "../ui/Interface.hpp"
 #include "../world/World.hpp"
 
@@ -10,6 +12,8 @@
 
 namespace blank {
 
 
 namespace blank {
 
+class HeadlessApplication;
+
 /// Parse and interpret arguemnts, then set up the environment and execute.
 class Runtime {
 
 /// Parse and interpret arguemnts, then set up the environment and execute.
 class Runtime {
 
@@ -27,6 +31,12 @@ public:
                ERROR,
        };
 
                ERROR,
        };
 
+       enum Target {
+               STANDALONE,
+               SERVER,
+               CLIENT,
+       };
+
        struct Config {
                bool vsync = true;
                bool doublebuf = true;
        struct Config {
                bool vsync = true;
                bool doublebuf = true;
@@ -36,7 +46,9 @@ public:
                std::string save_path;
                std::string world_name = "default";
 
                std::string save_path;
                std::string world_name = "default";
 
+               Client::Config client = Client::Config();
                Interface::Config interface = Interface::Config();
                Interface::Config interface = Interface::Config();
+               Server::Config server = Server::Config();
                World::Config world = World::Config();
        };
 
                World::Config world = World::Config();
        };
 
@@ -46,9 +58,17 @@ public:
 
        int Execute();
 
 
        int Execute();
 
+private:
+       void RunStandalone();
+       void RunServer();
+       void RunClient();
+
+       void Run(HeadlessApplication &);
+
 private:
        const char *name;
        Mode mode;
 private:
        const char *name;
        Mode mode;
+       Target target;
        std::size_t n;
        std::size_t t;
        Config config;
        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 (file)
index 0000000..d26f552
--- /dev/null
@@ -0,0 +1,46 @@
+#include "ServerState.hpp"
+
+#include "Environment.hpp"
+#include "TextureIndex.hpp"
+#include "../net/io.hpp"
+
+#include <iostream>
+
+
+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 (file)
index 0000000..fc9ac19
--- /dev/null
@@ -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
index 86ff0ad36c84852167f4bbb34b321dcdc7d8210b..f79bc60f03d7c96e6c0668b73ad62688fc0c44c2 100644 (file)
@@ -6,12 +6,12 @@
 
 namespace blank {
 
 
 namespace blank {
 
-class Application;
+class HeadlessApplication;
 class Viewport;
 
 struct State {
 
 class Viewport;
 
 struct State {
 
-       friend class Application;
+       friend class HeadlessApplication;
 
        virtual void Handle(const SDL_Event &) = 0;
 
 
        virtual void Handle(const SDL_Event &) = 0;
 
index 818bce5c0369ab0987d3736a28ea516bc52564c4..c072040bc12f57de2a31abc6a9465fbfa1e3a551 100644 (file)
@@ -6,7 +6,7 @@
 
 namespace blank {
 
 
 namespace blank {
 
-class Application;
+class HeadlessApplication;
 class State;
 
 class StateControl {
 class State;
 
 class StateControl {
@@ -29,7 +29,7 @@ public:
        }
 
 
        }
 
 
-       void Commit(Application &);
+       void Commit(HeadlessApplication &);
 
 private:
        enum Command {
 
 private:
        enum Command {
index 243b0a1dc519d42ba18d1e6350e60864896480ba..03abe36c163c4b7c120b725fd74a9d3a337f9bd8 100644 (file)
@@ -1,6 +1,7 @@
 #include "WorldState.hpp"
 
 #include "Environment.hpp"
 #include "WorldState.hpp"
 
 #include "Environment.hpp"
+#include "init.hpp"
 #include "TextureIndex.hpp"
 
 #include <SDL.h>
 #include "TextureIndex.hpp"
 
 #include <SDL.h>
@@ -23,8 +24,8 @@ WorldState::WorldState(
 , preload(env, world.Loader(), chunk_renderer)
 , unload(env, world.Loader()) {
        TextureIndex tex_index;
 , 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();
        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);
 
 void WorldState::OnEnter() {
        env.state.Push(&preload);
+       env.window.GrabMouse();
 }
 
 
 }
 
 
index 0e2a639f21053ad234353424efef7a2fd2383bd9..48a59502ad974dac9d98eed82d49eaaeaa13487b 100644 (file)
@@ -29,18 +29,29 @@ using std::string;
 
 namespace blank {
 
 
 namespace blank {
 
-Application::Application(Environment &e)
+HeadlessApplication::HeadlessApplication(HeadlessEnvironment &e)
 : env(e)
 , states() {
 
 }
 
 : env(e)
 , states() {
 
 }
 
+HeadlessApplication::~HeadlessApplication() {
+
+}
+
+
+Application::Application(Environment &e)
+: HeadlessApplication(e)
+, env(e) {
+
+}
+
 Application::~Application() {
        env.audio.StopAll();
 }
 
 
 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();
        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) {
        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 << '.';
        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();
        Uint32 last = SDL_GetTicks();
-       env.window.GrabMouse();
        while (HasState()) {
                Uint32 now = SDL_GetTicks();
                int delta = now - last;
        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);
 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();
 }
 
 
        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);
 void Application::HandleEvents() {
        env.counter.EnterHandle();
        SDL_Event event;
        while (HasState() && SDL_PollEvent(&event)) {
                Handle(event);
-               env.state.Commit(*this);
+               CommitStates();
        }
        env.counter.ExitHandle();
 }
        }
        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);
 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();
        }
        if (!states.empty()) {
                states.top()->OnPause();
        }
@@ -170,7 +211,7 @@ void Application::PushState(State *s) {
        s->OnResume();
 }
 
        s->OnResume();
 }
 
-State *Application::PopState() {
+State *HeadlessApplication::PopState() {
        State *s = states.top();
        states.pop();
        s->OnPause();
        State *s = states.top();
        states.pop();
        s->OnPause();
@@ -181,7 +222,7 @@ State *Application::PopState() {
        return s;
 }
 
        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;
        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;
 }
 
        return s_old;
 }
 
-State &Application::GetState() {
+State &HeadlessApplication::GetState() {
        return *states.top();
 }
 
        return *states.top();
 }
 
-bool Application::HasState() const noexcept {
+void HeadlessApplication::CommitStates() {
+       env.state.Commit(*this);
+}
+
+bool HeadlessApplication::HasState() const noexcept {
        return !states.empty();
 }
 
 
        return !states.empty();
 }
 
 
-void StateControl::Commit(Application &app) {
+void StateControl::Commit(HeadlessApplication &app) {
        while (!cue.empty()) {
                Memo m(cue.front());
                cue.pop();
        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/")
 : 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 &reg, TextureIndex &tex_index) const {
+void AssetLoader::LoadBlockTypes(const std::string &set_name, BlockTypeRegistry &reg, TextureIndex &tex_index) const {
        string full = data + set_name + ".types";
        std::ifstream file(full);
        if (!file) {
        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 &reg,
        }
 }
 
        }
 }
 
-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);
 }
 
        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());
 }
 
        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());
        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;
 }
 
        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) {
        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);
 }
 
        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()) {
        // TODO: where the hell should that size come from?
        tex.Reserve(16, 16, index.Size(), Format());
        for (const auto &entry : index.Entries()) {
index 18816a424bdb3a972341311d72a9cda19fa19ac6..81c2e9291cec57c61d039e223922c73d52adf8fd 100644 (file)
@@ -4,6 +4,7 @@
 #include <alut.h>
 #include <SDL.h>
 #include <SDL_image.h>
 #include <alut.h>
 #include <SDL.h>
 #include <SDL_image.h>
+#include <SDL_net.h>
 #include <SDL_ttf.h>
 #include <GL/glew.h>
 
 #include <SDL_ttf.h>
 #include <GL/glew.h>
 
@@ -20,6 +21,15 @@ std::string sdl_error_append(std::string msg) {
        return 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') {
 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()) {
 
 SDLError::SDLError()
 : std::runtime_error(SDL_GetError()) {
 
@@ -56,8 +77,8 @@ SDLError::SDLError(const std::string &msg)
 
 
 InitSDL::InitSDL() {
 
 
 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)");
 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()");
 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_sdl()
+, init_net() {
+
+}
+
+Init::Init(bool double_buffer, int sample_size)
+: init_video()
 , init_img()
 , init_ttf()
 , init_gl(double_buffer, sample_size)
 , init_img()
 , init_ttf()
 , init_gl(double_buffer, sample_size)
index 13bffa1f30e8886087ac441319f0f9af766f8aa3..66c3345fa170a108ee71189648a0384ddc54c2b5 100644 (file)
@@ -27,6 +27,15 @@ public:
 
 };
 
 
 };
 
+class NetError
+: public std::runtime_error {
+
+public:
+       NetError();
+       explicit NetError(const std::string &);
+
+};
+
 
 class InitSDL {
 
 
 class InitSDL {
 
@@ -40,6 +49,18 @@ public:
 };
 
 
 };
 
 
+class InitVideo {
+
+public:
+       InitVideo();
+       ~InitVideo();
+
+       InitVideo(const InitVideo &) = delete;
+       InitVideo &operator =(const InitVideo &) = delete;
+
+};
+
+
 class InitIMG {
 
 public:
 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:
 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);
 
 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;
        InitIMG init_img;
        InitTTF init_ttf;
        InitAL init_al;
index d77cf47c228fbbdbc573f1e5e26f2473f2ab00ed..2f0f82ec1522e845602236aa32cf745d0cfc90f6 100644 (file)
@@ -1,6 +1,8 @@
 #include "Application.hpp"
 #include "Application.hpp"
+#include "ClientState.hpp"
 #include "Environment.hpp"
 #include "Runtime.hpp"
 #include "Environment.hpp"
 #include "Runtime.hpp"
+#include "ServerState.hpp"
 #include "WorldState.hpp"
 
 #include "init.hpp"
 #include "WorldState.hpp"
 
 #include "init.hpp"
@@ -45,12 +47,20 @@ string default_save_path() {
 
 namespace blank {
 
 
 namespace blank {
 
+HeadlessEnvironment::HeadlessEnvironment(const string &asset_path)
+: loader(asset_path)
+, counter()
+, state() {
+
+}
+
 Environment::Environment(Window &win, const string &asset_path)
 Environment::Environment(Window &win, const string &asset_path)
-: audio()
+: HeadlessEnvironment(asset_path)
+, assets(loader)
+, audio()
 , viewport()
 , window(win)
 , viewport()
 , window(win)
-, assets(asset_path)
-, counter() {
+, keymap() {
        viewport.Clear();
        window.Flip();
        keymap.LoadDefault();
        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)
 Runtime::Runtime() noexcept
 : name("blank")
 , mode(NORMAL)
+, target(STANDALONE)
 , n(0)
 , t(0)
 , config() {
 , 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;
                                                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') {
                                        } 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 {
                                                        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') {
                                        } 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;
        }
 
                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);
 
        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);
        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);
        }
 
                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()) {
 
        WorldSave save(config.save_path + config.world_name + '/');
        if (save.Exists()) {
@@ -252,11 +339,22 @@ int Runtime::Execute() {
                save.Write(config.world);
        }
 
                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:
        switch (mode) {
                default:
                case NORMAL:
@@ -272,8 +370,6 @@ int Runtime::Execute() {
                        app.RunS(n, t);
                        break;
        }
                        app.RunS(n, t);
                        break;
        }
-
-       return 0;
 }
 
 }
 }
 
 }
diff --git a/src/net/Client.hpp b/src/net/Client.hpp
new file mode 100644 (file)
index 0000000..63c3fdd
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef BLANK_NET_CLIENT_HPP_
+#define BLANK_NET_CLIENT_HPP_
+
+#include "Connection.hpp"
+
+#include <string>
+#include <SDL_net.h>
+
+
+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 (file)
index 0000000..49e9cda
--- /dev/null
@@ -0,0 +1,42 @@
+#ifndef BLANK_NET_CONNECTION_HPP_
+#define BLANK_NET_CONNECTION_HPP_
+
+#include "../app/IntervalTimer.hpp"
+
+#include <SDL_net.h>
+
+
+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 (file)
index 0000000..7b482e5
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef BLANK_NET_PACKET_HPP_
+#define BLANK_NET_PACKET_HPP_
+
+#include <cstdint>
+
+
+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 (file)
index 0000000..b6a72a9
--- /dev/null
@@ -0,0 +1,47 @@
+#ifndef BLANK_NET_SERVER_HPP
+#define BLANK_NET_SERVER_HPP
+
+#include <list>
+#include <SDL_net.h>
+
+
+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<Connection> clients;
+
+       World &world;
+
+};
+
+}
+
+#endif
diff --git a/src/net/io.hpp b/src/net/io.hpp
new file mode 100644 (file)
index 0000000..d3fbba2
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef BLANK_NET_IO_HPP
+#define BLANK_NET_IO_HPP
+
+#include <iosfwd>
+
+
+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 (file)
index 0000000..73ed73c
--- /dev/null
@@ -0,0 +1,247 @@
+#include "Client.hpp"
+#include "Connection.hpp"
+#include "io.hpp"
+#include "Packet.hpp"
+#include "Server.hpp"
+
+#include "../app/init.hpp"
+
+#include <cstring>
+#include <iostream>
+
+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<const Packet *>(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<Packet *>(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<const unsigned char *>(&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<const Packet *>(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<Connection>::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;
+}
+
+}
index acf877fd1da6811dca1679d80461d7c86105f0ee..e6376614dacd3525bf4cfed6999d467bb99c2bea 100644 (file)
@@ -125,8 +125,8 @@ Interface::Interface(
 , remove_timer(256)
 , remove(0)
 , selection(0)
 , 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) {
 , fwd(0)
 , rev(0)
 , debug(false) {
index 3240c3de8349f14257b30fa8d08855faf200693a..fe4f6a66d0062a6a81fac5ed5fcee89a291d62f5 100644 (file)
@@ -11,7 +11,7 @@
 
 namespace blank {
 
 
 namespace blank {
 
-class Assets;
+class AssetLoader;
 class TextureIndex;
 class Viewport;
 class World;
 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);
 
        /// 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;
        void FogDensity(float d) noexcept { fog_density = d; }
 
        bool InRange(const Chunk::Pos &) const noexcept;
index f701faf724f4c9e407950f27cf9ef1907941260c..4a237e72cfa7cbd9ff68ba03606874c3f1f3f376 100644 (file)
@@ -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();
        block_tex.Bind();
-       assets.LoadTextures(tex_index, block_tex);
+       loader.LoadTextures(tex_index, block_tex);
        block_tex.FilterNearest();
 }
 
        block_tex.FilterNearest();
 }