+++ /dev/null
-#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);
-}
-
-}
+++ /dev/null
-#ifndef BLANK_APP_CLIENTSTATE_HPP_
-#define BLANK_APP_CLIENTSTATE_HPP_
-
-#include "State.hpp"
-#include "../net/Client.hpp"
-#include "../ui/Interface.hpp"
-#include "../world/BlockTypeRegistry.hpp"
-#include "../world/ChunkRenderer.hpp"
-#include "../world/World.hpp"
-
-
-namespace blank {
-
-class Environment;
-
-class ClientState
-: public State {
-
-public:
- ClientState(
- 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;
-
-private:
- Environment &env;
- BlockTypeRegistry block_types;
- World world;
- ChunkRenderer chunk_renderer;
- Interface interface;
- Client client;
-
-};
-
-}
-
-#endif
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;
StateControl state;
- explicit HeadlessEnvironment(const std::string &asset_path);
+ explicit HeadlessEnvironment(const Config &);
};
Keymap keymap;
- Environment(Window &win, const std::string &asset_path);
+ Environment(Window &win, const Config &);
};
#ifndef BLANK_RUNTIME_HPP_
#define BLANK_RUNTIME_HPP_
+#include "Environment.hpp"
#include "../net/Client.hpp"
#include "../net/Server.hpp"
#include "../ui/Interface.hpp"
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();
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 &);
SWITCH,
POP,
POP_ALL,
+ POP_AFTER,
+ POP_UNTIL,
};
struct Memo {
State *state;
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;
+ }
+ }
}
}
}
#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"
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()
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);
+ }
}
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;
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;
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) {
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);
}
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 {
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);
}
--- /dev/null
+#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
--- /dev/null
+#ifndef BLANK_CLIENT_INTERACTIVESTATE_HPP_
+#define BLANK_CLIENT_INTERACTIVESTATE_HPP_
+
+#include "../app/State.hpp"
+#include "../io/WorldSave.hpp"
+#include "../ui/Interface.hpp"
+#include "../world/BlockTypeRegistry.hpp"
+#include "../world/ChunkRenderer.hpp"
+#include "../world/World.hpp"
+
+
+namespace blank {
+
+class Environment;
+
+namespace client {
+
+class MasterState;
+
+class InteractiveState
+: public State {
+
+public:
+ explicit InteractiveState(MasterState &);
+
+ World &GetWorld() noexcept { return world; }
+ Interface &GetInterface() noexcept { return interface; }
+
+ void OnEnter() override;
+
+ void Handle(const SDL_Event &) override;
+ void Update(int dt) override;
+ void Render(Viewport &) override;
+
+private:
+ MasterState &master;
+ BlockTypeRegistry block_types;
+ WorldSave save;
+ World world;
+ ChunkRenderer chunk_renderer;
+ Interface interface;
+
+};
+
+}
+}
+
+#endif
--- /dev/null
+#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 <memory>
+
+
+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<InteractiveState> state;
+ Client client;
+
+ InitialState init_state;
+
+};
+
+}
+}
+
+#endif
--- /dev/null
+#include "InitialState.hpp"
+#include "InteractiveState.hpp"
+#include "MasterState.hpp"
+
+#include "../app/Environment.hpp"
+#include "../app/init.hpp"
+#include "../app/TextureIndex.hpp"
+
+#include <iostream>
+
+
+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();
+}
+
+}
+}
};
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 &);
void HandlePacket(const UDPpacket &);
private:
- World &world;
Connection conn;
UDPsocket client_sock;
UDPpacket client_pack;
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;
void FlagRecv() noexcept;
private:
+ PacketHandler *handler;
IPaddress addr;
IntervalTimer send_timer;
IntervalTimer recv_timer;
#include <cstdint>
#include <ostream>
#include <string>
+#include <SDL_net.h>
namespace blank {
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;
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<class T>
+ void Write(const T &, size_t off) noexcept;
+ template<class T>
+ 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<class PayloadType>
+ PayloadType As() {
+ PayloadType result;
+ result.length = PayloadType::MAX_LEN;
+ result.data = &payload[0];
+ return result;
+ }
+
+ template<class PayloadType>
+ 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<class PayloadType>
+ static PayloadType Make(UDPpacket &udp_pack) {
+ Packet &pack = *reinterpret_cast<Packet *>(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;
+ }
+
+};
}
--- /dev/null
+#ifndef BLANK_NET_PACKETHANDLER_HPP_
+#define BLANK_NET_PACKETHANDLER_HPP_
+
+#include "Packet.hpp"
+
+#include <SDL_net.h>
+
+
+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
#include "Connection.hpp"
#include "io.hpp"
#include "Packet.hpp"
+#include "PacketHandler.hpp"
#include "Server.hpp"
#include "../app/init.hpp"
}
-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)];
}
void Client::SendLogin(const string &name) {
- Packet &pack = *reinterpret_cast<Packet *>(client_pack.data);
- client_pack.len = pack.MakeLogin(name);
+ auto pack = Packet::Make<Packet::Login>(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 }
void Connection::Send(UDPpacket &udp_pack, UDPsocket sock) {
Packet &pack = *reinterpret_cast<Packet *>(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) {
void Connection::Received(const UDPpacket &udp_pack) {
Packet &pack = *reinterpret_cast<Packet *>(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);
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<Packet *>(udp_pack.data);
- udp_pack.len = pack.MakePing();
+ Packet::Make<Packet::Ping>(udp_pack);
Send(udp_pack, sock);
}
}
-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<class T>
+void Packet::Payload::Write(const T &src, size_t off) noexcept {
+ if ((length - off) < sizeof(T)) {
+ // dismiss out of bounds write
+ return;
+ }
+ *reinterpret_cast<T *>(&data[off]) = src;
}
-size_t Packet::MakePing() noexcept {
- Tag();
- header.type = PING;
- return sizeof(Header);
+template<class T>
+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<T *>(&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<uint32_t *>(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<glm::ivec3 *>(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<glm::vec3 *>(cursor) = player.Position();
- cursor += 12;
- *reinterpret_cast<glm::vec3 *>(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<glm::quat *>(cursor) = player.Orientation();
- cursor += 16;
- *reinterpret_cast<glm::vec3 *>(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<const Packet *>(udp_pack.data);
+ switch (pack.Type()) {
+ case Packet::Ping::TYPE:
+ On(Packet::As<Packet::Ping>(udp_pack));
+ break;
+ case Packet::Login::TYPE:
+ On(Packet::As<Packet::Login>(udp_pack));
+ break;
+ case Packet::Join::TYPE:
+ On(Packet::As<Packet::Join>(udp_pack));
+ break;
+ case Packet::Part::TYPE:
+ On(Packet::As<Packet::Part>(udp_pack));
+ break;
+ default:
+ // drop unknown or unhandled packets
+ break;
+ }
}
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:
void Server::HandleLogin(Connection &client, const UDPpacket &udp_pack) {
- const Packet &pack = *reinterpret_cast<const Packet *>(udp_pack.data);
- size_t maxlen = min(udp_pack.len - int(sizeof(Packet::Header)), 32);
+ auto pack = Packet::As<Packet::Login>(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<Packet *>(serv_pack.data);
if (player) {
// success!
cout << "accepted login from player \"" << name << '"' << endl;
- response.MakeJoin(*player, world.Name());
+ auto response = Packet::Make<Packet::Join>(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<Packet::Part>(serv_pack);
client.Send(serv_pack, serv_sock);
client.Close();
}