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))
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
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/
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
--no-vsync
disable vsync
+--standalone
+ run as standalone (the default)
+
+--client
+ run as client
+
+--server
+ run as server
+
Interface
---------
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
-----
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);
/// 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 &);
/// 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<State *> states;
};
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;
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 &);
+
};
}
--- /dev/null
+#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) {
+
+}
+
+}
--- /dev/null
+#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
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);
};
#ifndef BLANK_RUNTIME_HPP_
#define BLANK_RUNTIME_HPP_
+#include "../net/Client.hpp"
+#include "../net/Server.hpp"
#include "../ui/Interface.hpp"
#include "../world/World.hpp"
namespace blank {
+class HeadlessApplication;
+
/// Parse and interpret arguemnts, then set up the environment and execute.
class Runtime {
ERROR,
};
+ enum Target {
+ STANDALONE,
+ SERVER,
+ CLIENT,
+ };
+
struct Config {
bool vsync = true;
bool doublebuf = true;
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();
};
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;
--- /dev/null
+#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) {
+
+}
+
+}
--- /dev/null
+#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
namespace blank {
-class Application;
+class HeadlessApplication;
class Viewport;
struct State {
- friend class Application;
+ friend class HeadlessApplication;
virtual void Handle(const SDL_Event &) = 0;
namespace blank {
-class Application;
+class HeadlessApplication;
class State;
class StateControl {
}
- void Commit(Application &);
+ void Commit(HeadlessApplication &);
private:
enum Command {
#include "WorldState.hpp"
#include "Environment.hpp"
+#include "init.hpp"
#include "TextureIndex.hpp"
#include <SDL.h>
, 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();
void WorldState::OnEnter() {
env.state.Push(&preload);
+ env.window.GrabMouse();
}
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();
}
}
-void Application::RunT(size_t t) {
+void HeadlessApplication::RunT(size_t t) {
Uint32 last = SDL_GetTicks();
Uint32 finish = last + t;
while (HasState() && last < finish) {
}
}
-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 << '.';
}
-void Application::Run() {
+void HeadlessApplication::Run() {
Uint32 last = SDL_GetTicks();
- env.window.GrabMouse();
while (HasState()) {
Uint32 now = SDL_GetTicks();
int delta = now - last;
}
}
+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();
}
}
}
+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::PushState(State *s) {
+void HeadlessApplication::PushState(State *s) {
if (!states.empty()) {
states.top()->OnPause();
}
s->OnResume();
}
-State *Application::PopState() {
+State *HeadlessApplication::PopState() {
State *s = states.top();
states.pop();
s->OnPause();
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;
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();
}
-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)) {
}
}
-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) {
}
}
-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());
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) {
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()) {
#include <alut.h>
#include <SDL.h>
#include <SDL_image.h>
+#include <SDL_net.h>
#include <SDL_ttf.h>
#include <GL/glew.h>
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') {
}
+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()) {
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)");
}
}
}
+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)");
}
+InitNet::InitNet() {
+ if (SDLNet_Init() != 0) {
+ throw SDLError("SDLNet_Init()");
+ }
+}
+
+InitNet::~InitNet() {
+ SDLNet_Quit();
+}
+
+
InitTTF::InitTTF() {
if (TTF_Init() != 0) {
throw SDLError("TTF_Init()");
}
-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)
};
+class NetError
+: public std::runtime_error {
+
+public:
+ NetError();
+ explicit NetError(const std::string &);
+
+};
+
class InitSDL {
};
+class InitVideo {
+
+public:
+ InitVideo();
+ ~InitVideo();
+
+ InitVideo(const InitVideo &) = delete;
+ InitVideo &operator =(const InitVideo &) = delete;
+
+};
+
+
class InitIMG {
public:
};
+class InitNet {
+
+public:
+ InitNet();
+ ~InitNet();
+
+ InitNet(const InitNet &) = delete;
+ InitNet &operator =(const InitNet &) = delete;
+
+};
+
+
class InitTTF {
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;
#include "Application.hpp"
+#include "ClientState.hpp"
#include "Environment.hpp"
#include "Runtime.hpp"
+#include "ServerState.hpp"
#include "WorldState.hpp"
#include "init.hpp"
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();
Runtime::Runtime() noexcept
: name("blank")
, mode(NORMAL)
+, target(STANDALONE)
, n(0)
, t(0)
, config() {
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 {
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') {
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);
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()) {
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:
app.RunS(n, t);
break;
}
-
- return 0;
}
}
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#ifndef BLANK_NET_IO_HPP
+#define BLANK_NET_IO_HPP
+
+#include <iosfwd>
+
+
+namespace blank {
+
+std::ostream &operator <<(std::ostream &, const IPaddress &);
+
+}
+
+#endif
--- /dev/null
+#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;
+}
+
+}
, 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) {
namespace blank {
-class Assets;
+class AssetLoader;
class TextureIndex;
class Viewport;
class World;
/// 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 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();
}