Merge pull request #251 from GentenStudios/feat-enet

WIP - Feat enet
develop
Vyom Fadia 2020-07-03 13:01:20 +01:00 committed by GitHub
commit a8c9943ac9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 4838 additions and 145 deletions

3
.gitmodules vendored
View File

@ -7,6 +7,9 @@
[submodule "Phoenix/ThirdParty/entt"]
path = Phoenix/ThirdParty/entt
url = https://github.com/skypjack/entt.git
[submodule "Phoenix/ThirdParty/enet"]
path = Phoenix/ThirdParty/enet
url = https://github.com/lsalzman/enet.git
[submodule "Phoenix/ThirdParty/json"]
path = Phoenix/ThirdParty/json
url = https://github.com/nlohmann/json.git

View File

@ -73,4 +73,6 @@ available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.ht
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
https://www.contributor-covenant.org/faq
#### </b> {#codeofconduct}

View File

@ -131,7 +131,7 @@ Multiline comments should use the /* */ method of commenting, where the /* and *
When documenting classes, methods and similar we follow the Doxygen Javadoc syntax. For example, this convention should be followed:
```cpp
/**
* @brief This class is for documentation reasons.
* @brief This class is for documentation reasons.
*/
class FooBar {
public:

View File

@ -0,0 +1,11 @@
# Phoenix Architecture
The goal of these documents is to provide an understanding of the architecture of Phoenix. If you're just starting to
contribute to this project, this is a good place to start to figure out how things work. If you have questions that are
not covered by these documents, reach out to [\#help](https://discord.gg/bPHVcxv) in our discord.
* [Networking][networking]
[networking]: Networking.md
[networking]: @ref networking
#### </b> {#architecture}

View File

@ -829,7 +829,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = ../Phoenix
INPUT = ../
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@ -871,7 +871,7 @@ FILE_PATTERNS = *.c \
*.md \
*.mm \
*.dox \
*.doc
*.doc
# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
@ -995,7 +995,7 @@ FILTER_SOURCE_PATTERNS =
# (index.html). This can be useful if you have a project on for instance GitHub
# and want to reuse the introduction page also for the doxygen output.
USE_MDFILE_AS_MAINPAGE = README.md
USE_MDFILE_AS_MAINPAGE = ../README.md
#---------------------------------------------------------------------------
# Configuration options related to source browsing

View File

@ -0,0 +1,71 @@
# Networking Architecure
This is a generic overview of how the networking system works. If you have additional questions beyond what is
described here, reach out in [#networking](https://discord.gg/6vwjZhC) on Discord. If you are interested in contributing
to the networking system, this is also the place to reach out to coordinate any work you want to do.
## Flow
The data in the networking system is managed in a deterministic lockstep method. This means the server is 100%
authoritative as to what happens. For some background information on deterministic lockstep check out
[this article by GlenFelder (Gaffer On Games) is a good read](https://gafferongames.com/post/deterministic_lockstep/).
We process three types of information during runtime. States are non-critical but time-sensitive pieces of information
that get sent unreliably and are discarded when not received (such as positioning). Events are critical pieces of
information that are sent via Enet's reliable packet system (Such as when a block is broken). Messages are essentially
events but we use a third channel on Enet since they are processed by an entirely separate system.
NOTE: This system's V1 version is a WIP, some information here may describe a future state of the networking system not
yet implemented.
### States
[InputState]s are created by the client every ∆T / game tick (1/20 of a second by default) and queued for transmission.
This happens in a thread so we never run out of time before the next ∆T.
The networking system is then running in another thread `m_iris` watching that queue. It packs states into redundant
packages (of 5 by default) so each packet will include the X most recent states. This helps prevent packet loss from
becoming an issue.
When the server gets packets, the networking thread (`m_iris` again) unpacks the data and fills a new queue system with
any packets it doesn't already have discarding any data it does have or arrived too late. This queue system contains
StateBundles which are a bundle of InputStates, one from each player for that tick (sequence). When either we have an
InputState from each connected player, or we have waited too long (.5 seconds max by default) we mark that bundle ready
for consumption.
A separate game thread on the server then watches that queue for when the networking system has marked that the oldest
stateBundle in the queue is ready. When it is the game thread takes that bundle, as well as any queued events or
messages, and processes them moving the server forward a tick. Once a tick is done processing, the server blasts any
relevant information to clients to clients updating them on the final official state for that tick.
When a client gets information back its usually significantly (up to 2.5 seconds at the extreme by default) later than
it sent that packet. While the client is waiting for that state from the server, it is predicting what is happening
based on velocities, AI logic, or the known player input state. When the client does get the packet for that state, it
checks to see if its predictions up until that point were correct, if they aren't (within a margin of error) then it
re-processes every state from that confirmation re-predicting all future states up to the one the client is currently
on.
### Events
Events are fortunately a lot simpler than States. Events are things such as an inventory movement (client to server) or
a block breaking (server to client). We use events when the player loads a new chunk to avoid sending all loaded
chunks every game-tick (we only update data if it changes).
Events aren't always a circular path, but can be. When an event occurs on the client, lets take inventory movement for
example, we send a message to the server letting the server know it happened. The server then processes that event
determining if that was a valid event, and sends an update event back to the client to confirm that it happened, or a
rejection event back to the client saying it didn't happen. If it happened then the client goes along with its day, if
it didn't actually happen, we roll back that action.
When the client sends the server information, it just sends a packet and the server processes that event on the next
game tick. When the server sends the client an event, it includes the tick number in case the client needs to do any
rollback. This means, the event may happen at a slightly different time on the client as the server (which is okay as
long as the event did actually happen). Not binding the events to ticks on the client side, helps allow the game ticks
to run freely and unbound if the client has the power to do so (of if the client does not have enough power, its okay
for it to lag without doing much thinking).
### Messages
Messages are just like events with a few key differences.
* When processed on the server, they are always sent to the Commander so they don't have header information determining
what system they go to.
* Messages dont have to have a return to the client to ack being received. We trust Enet's reliable packet system to
ensure they are delivered. Its up to the Commander/ the specific command to send the error message back when something
goes wrong.
* Messages from the Server to the Client are purely information and printed to the terminal with little to no client
side processing. If a Message from the client results in an action (EX: /tp) then there is no client side prediction and
the Event system is instead used to relay that the action happened.
[InputState]: @ref phx::InputState
#### </b> {#networking}

View File

@ -4,21 +4,23 @@ add_subdirectory(Audio)
set(currentDir ${CMAKE_CURRENT_LIST_DIR})
set(Headers
${eventHeaders}
${graphicsHeaders}
${audioHeaders}
${eventHeaders}
${graphicsHeaders}
${audioHeaders}
${currentDir}/Player.hpp
${currentDir}/Player.hpp
${currentDir}/InputQueue.hpp
${currentDir}/Client.hpp
${currentDir}/SplashScreen.hpp
${currentDir}/Crosshair.hpp
${currentDir}/Game.hpp
${currentDir}/EscapeMenu.hpp
${currentDir}/InputMap.hpp
${currentDir}/Client.hpp
${currentDir}/SplashScreen.hpp
${currentDir}/Crosshair.hpp
${currentDir}/Game.hpp
${currentDir}/EscapeMenu.hpp
${currentDir}/InputMap.hpp
${currentDir}/Network.hpp
${currentDir}/DebugOverlay.hpp
${currentDir}/GameTools.hpp
${currentDir}/DebugOverlay.hpp
${currentDir}/GameTools.hpp
PARENT_SCOPE
)
PARENT_SCOPE
)

View File

@ -28,18 +28,20 @@
#pragma once
#include <Client/Crosshair.hpp>
#include <Client/EscapeMenu.hpp>
#include <Client/GameTools.hpp>
#include <Client/Graphics/Camera.hpp>
#include <Client/Graphics/Layer.hpp>
#include <Client/Graphics/ShaderPipeline.hpp>
#include <Client/Graphics/UI.hpp>
#include <Client/Graphics/Window.hpp>
#include <Client/Player.hpp>
#include <Client/EscapeMenu.hpp>
#include <Client/Crosshair.hpp>
#include <Client/InputQueue.hpp>
#include <Common/CMS/ModManager.hpp>
#include <deque>
namespace phx::client
{
/**
@ -69,11 +71,20 @@ namespace phx::client
void onEvent(events::Event& e) override;
void tick(float dt) override;
/** @brief Sends a message packet to the server for the commander to
* interpret.
*
* @param input The message sent to the server
* @param cout Needs to be depreciated, unused (but required by
* terminal)
*/
void sendMessage(const std::string& input, std::ostringstream& cout);
private:
gfx::Window* m_window;
gfx::FPSCamera* m_camera = nullptr;
entt::registry* m_registry;
Player* m_player;
entt::registry* m_registry;
Player* m_player;
voxels::ChunkView* m_world = nullptr;
gfx::ShaderPipeline m_renderPipeline;
@ -81,17 +92,20 @@ namespace phx::client
ui::ChatWindow* m_chat = nullptr;
cms::ModManager* m_modManager;
Crosshair* m_crosshair = nullptr;
Crosshair* m_crosshair = nullptr;
EscapeMenu* m_escapeMenu = nullptr;
GameTools* m_gameDebug = nullptr;
bool m_followCam = true;
math::vec3 m_prevPos;
int m_playerHand = 0;
GameTools* m_gameDebug = nullptr;
bool m_followCam = true;
math::vec3 m_prevPos;
int m_playerHand = 0;
client::Network* m_network;
client::InputQueue* m_inputQueue;
// intermediary variables to prevent getting the pointer from the client
// singleton every tick.
audio::Audio* m_audio;
audio::Audio* m_audio;
audio::Listener* m_listener;
};
} // namespace phx::client

View File

@ -0,0 +1,92 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <Client/InputMap.hpp>
#include <Client/Network.hpp>
#include <Client/Player.hpp>
#include <Common/Util/BlockingQueue.hpp>
namespace phx::client
{
class InputQueue
{
public:
InputQueue(entt::registry* registry, Player* player);
~InputQueue();
/**
* @brief Thread to capture and queue input states
* @param dt How frequently in seconds a state should be captured in
* milliseconds
* @param network Pointer to network to send states to
*/
private:
void run(std::chrono::milliseconds dt, client::Network* network);
public:
/**
* @brief Starts a new thread to capture and queue input states
* @param dt How frequently in seconds a state should be captured in
* milliseconds
* @param network Pointer to network to send states to
*/
void start(std::chrono::milliseconds dt, client::Network* network);
/**
* @brief Stops the current thread
*/
void stop();
/**
* @brief Gets the current state of the input
* @return The input state
*/
InputState getCurrentState();
BlockingQueue<InputState> m_queue;
private:
bool m_running;
std::thread m_thread;
Player* m_player;
entt::registry* m_registry;
std::size_t m_sequence;
client::Input* m_forward;
client::Input* m_backward;
client::Input* m_left;
client::Input* m_right;
client::Input* m_up;
client::Input* m_down;
};
} // namespace phx::client

View File

@ -0,0 +1,99 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <Common/Input.hpp>
#include <Common/Network/Host.hpp>
#include <Common/Util/BlockingQueue.hpp>
#include <thread>
namespace phx::client
{
class Network
{
public:
Network(std::ostringstream& chat);
~Network();
private:
void run();
public:
void start();
void stop();
private:
/**
* @brief Actions taken when a state is received
*
* @param data The data in the state packet
* @param dataLength The length of the data in the state packet
*/
void parseEvent(net::Packet& packet);
/**
* @brief Actions taken when a state is received
*
* @param data The data in the state packet
* @param dataLength The length of the data in the state packet
*/
void parseState(net::Packet& packet);
/**
* @brief Actions taken when a message is received
*
* @param data The data in the message packet
* @param dataLength The length of the data in the message packet
*/
void parseMessage(phx::net::Packet& packet);
public:
/**
* @brief Sends a state packet to a client
*
* @param userRef The user to sent the state to
* @param data The state packet data
*/
void sendState(InputState inputState);
/**
* @brief Sends a message packet to a client
*
* @param userRef The user to sent the message to
* @param data The message packet data
*/
void sendMessage(std::string message);
private:
bool m_running;
phx::net::Host* m_client;
std::ostringstream& m_chat;
std::thread m_thread;
};
} // namespace phx::client::net

View File

@ -3,22 +3,24 @@ add_subdirectory(Audio)
set(currentDir ${CMAKE_CURRENT_LIST_DIR})
set(Sources
${graphicsSources}
${audioSources}
${graphicsSources}
${audioSources}
${currentDir}/Player.cpp
${currentDir}/Player.cpp
${currentDir}/InputQueue.cpp
${currentDir}/Client.cpp
${currentDir}/SplashScreen.cpp
${currentDir}/Crosshair.cpp
${currentDir}/Game.cpp
${currentDir}/EscapeMenu.cpp
${currentDir}/InputMap.cpp
${currentDir}/Client.cpp
${currentDir}/SplashScreen.cpp
${currentDir}/Crosshair.cpp
${currentDir}/Game.cpp
${currentDir}/EscapeMenu.cpp
${currentDir}/InputMap.cpp
${currentDir}/Network.cpp
${currentDir}/DebugOverlay.cpp
${currentDir}/GameTools.cpp
${currentDir}/DebugOverlay.cpp
${currentDir}/GameTools.cpp
${currentDir}/Main.cpp
${currentDir}/Main.cpp
PARENT_SCOPE
)
PARENT_SCOPE
)

View File

@ -119,11 +119,13 @@ void Client::onEvent(events::Event e)
void Client::run()
{
Settings::get()->load("settings.txt");
Logger::initialize({});
LoggerConfig config;
config.verbosity = LogVerbosity::DEBUG;
Logger::initialize(config);
audio::Audio::initialize();
m_audio = new audio::Audio();
SplashScreen* splashScreen = new SplashScreen();
m_layerStack.pushLayer(splashScreen);

View File

@ -29,11 +29,14 @@
#include <Client/Client.hpp>
#include <Client/Game.hpp>
#include <Common/Actor.hpp>
#include <Common/CMS/ModManager.hpp>
#include <Common/Serialization/Serializer.hpp>
#include <Common/Voxels/BlockRegistry.hpp>
#include <Common/Actor.hpp>
#include <Common/Commander.hpp>
#include <Common/Position.hpp>
#include <Common/Voxels/BlockRegistry.hpp>
#include <Common/Logger.hpp>
#include <cmath>
#include <tuple>
@ -41,11 +44,18 @@
using namespace phx::client;
using namespace phx;
static Commander kirk;
///@todo This needs refactored to play nicely
/**
* This exists so we can call the message function from the chat client,
* we definitely need to just clean that up so it all plays nicely. If
* a second instance of the game is created, this entire system will break
* (but so will a few others . . . )
*/
static Game* myGame = nullptr;
static void rawEcho(const std::string& input, std::ostringstream& cout)
{
kirk.callback(input, cout);
myGame->sendMessage(input, cout);
}
Game::Game(gfx::Window* window, entt::registry* registry)
@ -59,7 +69,7 @@ Game::Game(gfx::Window* window, entt::registry* registry)
fileStream.open("Saves/" + save + "/Mods.txt");
if (!fileStream.is_open())
{
std::cout << "Error opening save file";
LOG_FATAL("CMS") << "Error opening save file";
exit(EXIT_FAILURE);
}
@ -73,6 +83,10 @@ Game::Game(gfx::Window* window, entt::registry* registry)
voxels::BlockRegistry::get()->registerAPI(m_modManager);
m_modManager->registerFunction(
"core.command.register",
[](std::string command, std::string help, sol::function f) {});
m_modManager->registerFunction("core.print", [=](const std::string& text) {
m_chat->cout << text << "\n";
});
@ -156,7 +170,7 @@ Game::Game(gfx::Window* window, entt::registry* registry)
float x = source["position"]["x"];
float y = source["position"]["y"];
float z = source["position"]["z"];
audioSource.setPos({x, y, z});
}
@ -230,19 +244,23 @@ Game::Game(gfx::Window* window, entt::registry* registry)
Client::get()->getAudioPool()->queue(audioSource);
});
myGame = this;
}
Game::~Game() { delete m_chat; }
void Game::onAttach()
{
/// @todo Replace this with logger
printf("%s", "Attaching game layer\n");
m_chat = new ui::ChatWindow("Chat Window", 5,
"Type /help for a command list and help.");
/// @TODO replace with network callback
m_chat->registerCallback(rawEcho);
m_network = new client::Network(m_chat->cout);
m_network->start();
m_player = new Player(m_registry);
m_player->registerAPI(m_modManager);
@ -251,11 +269,11 @@ void Game::onAttach()
if (!result.ok)
{
LOG_FATAL("MODDING") << "An error has occured.";
LOG_FATAL("MODDING") << "An error has occurred.";
exit(EXIT_FAILURE);
}
printf("%s", "Registering world\n");
LOG_INFO("MAIN") << "Registering world";
const std::string save = "save1";
m_world = new voxels::ChunkView(3, voxels::Map(save, "map1"));
m_player->setWorld(m_world);
@ -266,7 +284,7 @@ void Game::onAttach()
m_player->getEntity(),
voxels::BlockRegistry::get()->getFromRegistryID(0));
printf("%s", "Prepare rendering\n");
LOG_INFO("MAIN") << "Prepare rendering";
m_renderPipeline.prepare("Assets/SimpleWorld.vert",
"Assets/SimpleWorld.frag",
gfx::ChunkRenderer::getRequiredShaderLayout());
@ -276,7 +294,7 @@ void Game::onAttach()
const math::mat4 model;
m_renderPipeline.setMatrix("u_model", model);
printf("%s", "Register GUI\n");
LOG_INFO("MAIN") << "Register GUI";
m_crosshair = new Crosshair(m_window);
m_escapeMenu = new EscapeMenu(m_window);
Client::get()->pushLayer(m_crosshair);
@ -287,14 +305,20 @@ void Game::onAttach()
new GameTools(&m_followCam, &m_playerHand, m_player, m_registry);
Client::get()->pushLayer(m_gameDebug);
}
printf("%s", "Game layer attached");
m_inputQueue = new InputQueue(m_registry, m_player);
m_inputQueue->start(std::chrono::milliseconds(50), m_network);
LOG_INFO("MAIN") << "Game layer attached";
}
void Game::onDetach()
{
m_network->stop();
delete m_world;
delete m_player;
delete m_camera;
delete m_network;
}
void Game::onEvent(events::Event& e)
@ -402,7 +426,7 @@ void Game::tick(float dt)
m_camera->tick(dt);
const Position& position = m_registry->get<Position>(m_player->getEntity());
if (m_followCam)
{
m_prevPos = position.position;
@ -425,4 +449,9 @@ void Game::tick(float dt)
m_world->render();
m_player->renderSelectionBox(m_camera->calculateViewMatrix(),
m_camera->getProjection());
}
}
void Game::sendMessage(const std::string& input, std::ostringstream& cout)
{
m_network->sendMessage(input);
}

View File

@ -188,4 +188,3 @@ void FPSCamera::onWindowResize(events::Event e)
}
void FPSCamera::setActor(entt::entity actor) { m_actor = actor;}

View File

@ -0,0 +1,99 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#include <Client/InputQueue.hpp>
#include <Common/Position.hpp>
using namespace phx::client;
using namespace phx;
InputQueue::InputQueue(entt::registry* registry, Player* player)
: m_player(player), m_registry(registry),
m_forward(InputMap::get()->getInput("core.move.forward")),
m_backward(InputMap::get()->getInput("core.move.backward")),
m_left(InputMap::get()->getInput("core.move.left")),
m_right(InputMap::get()->getInput("core.move.right")),
m_up(InputMap::get()->getInput("core.move.up")),
m_down(InputMap::get()->getInput("core.move.down"))
{
}
InputQueue::~InputQueue()
{
if (m_running)
{
stop();
}
}
void InputQueue::run(std::chrono::milliseconds dt, client::Network* network)
{
using std::chrono::system_clock;
system_clock::time_point next = system_clock::now();
while (m_running)
{
m_sequence++;
InputState state = getCurrentState();
m_queue.push(state);
network->sendState(state);
next = next + dt;
std::this_thread::sleep_until(next);
}
}
void InputQueue::start(std::chrono::milliseconds dt, client::Network* network)
{
m_running = true;
std::thread thread1 = std::thread(&InputQueue::run, this, dt, network);
std::swap(m_thread, thread1);
}
void InputQueue::stop()
{
m_running = false;
m_thread.join();
}
InputState InputQueue::getCurrentState()
{
InputState input;
input.forward = InputMap::get()->getState(m_forward);
input.backward = InputMap::get()->getState(m_backward);
input.left = InputMap::get()->getState(m_left);
input.right = InputMap::get()->getState(m_right);
input.up = InputMap::get()->getState(m_up);
input.down = InputMap::get()->getState(m_down);
input.rotation.x = static_cast<unsigned int>(
m_registry->get<Position>(m_player->getEntity()).rotation.x * 360000.0);
input.rotation.y = static_cast<unsigned int>(
m_registry->get<Position>(m_player->getEntity()).rotation.y * 360000.0);
input.sequence = m_sequence;
return input;
}

View File

@ -0,0 +1,132 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#include <Client/Network.hpp>
#include <Common/Logger.hpp>
using namespace phx::client;
Network::Network(std::ostringstream& chat) : m_chat(chat)
{
m_client = new phx::net::Host();
m_client->onReceive([this](phx::net::Peer& peer, phx::net::Packet&& packet,
enet_uint32 channelID) {
switch (channelID)
{
case 0:
parseEvent(packet);
break;
case 1:
parseState(packet);
break;
case 2:
parseMessage(packet);
break;
}
});
m_client->onDisconnect([this](std::size_t peerID, enet_uint32) {
std::cout << "Server disconnected";
});
phx::net::Address address = phx::net::Address("127.0.0.1", 7777);
m_client->connect(address, 3);
m_client->poll(5000_ms);
}
Network::~Network()
{
if (m_running)
stop();
delete m_client;
}
void Network::run()
{
while (m_running)
{
m_client->poll();
}
}
void Network::start()
{
m_running = true;
std::thread thread1 = std::thread(&Network::run, this);
std::swap(m_thread, thread1);
}
void Network::stop()
{
m_running = false;
m_thread.join();
}
void Network::parseEvent(phx::net::Packet& packet)
{
std::cout << "Event received";
}
void Network::parseState(phx::net::Packet& packet) {}
void Network::parseMessage(phx::net::Packet& packet)
{
std::string input;
auto data = packet.getData();
phx::Serializer ser(Serializer::Mode::READ);
ser.setBuffer(reinterpret_cast<std::byte*>(data.data()), data.size());
ser& input;
LOG_INFO("Messenger") << input;
m_chat << input;
m_chat << "\n";
}
void Network::sendState(InputState inputState)
{
Serializer ser(Serializer::Mode::WRITE);
ser& inputState;
auto state = ser.getBuffer();
phx::net::Packet packet =
phx::net::Packet(ser.getBuffer(), phx::net::PacketFlags::UNRELIABLE);
m_client->broadcast(packet, 1);
}
void Network::sendMessage(std::string message)
{
Serializer ser(Serializer::Mode::WRITE);
ser& message;
phx::net::Packet packet =
phx::net::Packet(ser.getBuffer(), phx::net::PacketFlags::RELIABLE);
m_client->broadcast(packet, 2);
}

View File

@ -33,8 +33,22 @@
* @copyright Copyright (c) 2019-2020 Genten Studios
*/
namespace phx{
struct Hand{
voxels::BlockType* hand;
};
}
#include <Common/Input.hpp>
#include <Common/Voxels/Block.hpp>
#include <entt/entt.hpp>
namespace phx
{
struct Hand
{
voxels::BlockType* hand;
};
class ActorSystem
{
public:
static entt::entity registerActor(entt::registry* registry);
static void tick(entt::registry* registry, entt::entity entity,
const float dt, InputState input);
};
} // namespace phx

View File

@ -1,23 +1,28 @@
add_subdirectory(Math)
add_subdirectory(Voxels)
add_subdirectory(CMS)
add_subdirectory(Serialization)
add_subdirectory(Network)
set(currentDir ${CMAKE_CURRENT_LIST_DIR})
set(Headers
${mathHeaders}
${voxelHeaders}
${cmsHeaders}
${serializationHeaders}
${networkHeaders}
${currentDir}/CoreIntrinsics.hpp
${currentDir}/EnumTools.hpp
${currentDir}/Singleton.hpp
${currentDir}/FileIO.hpp
${currentDir}/Logger.hpp
${currentDir}/CoreIntrinsics.hpp
${currentDir}/EnumTools.hpp
${currentDir}/Singleton.hpp
${currentDir}/FileIO.hpp
${currentDir}/Logger.hpp
${currentDir}/Settings.hpp
${currentDir}/Commander.hpp
${currentDir}/Position.hpp
${currentDir}/Movement.hpp
${currentDir}/Settings.hpp
${currentDir}/Commander.hpp
${currentDir}/Position.hpp
${currentDir}/Movement.hpp
${currentDir}/Input.hpp
PARENT_SCOPE
)
PARENT_SCOPE
)

View File

@ -0,0 +1,49 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <Common/Math/Math.hpp>
#include <Common/Serialization/Serializer.hpp>
#include <cstddef>
namespace phx
{
struct InputState : public phx::ISerializable
{
bool forward{};
bool backward{};
bool left{};
bool right{};
bool up{};
bool down{};
math::vec2u rotation{}; // in 1/1000 degres
std::size_t sequence{};
Serializer& operator&(Serializer& this_) override;
};
} // namespace phx

View File

@ -0,0 +1,155 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <enet/enet.h>
#include <string>
namespace phx::net
{
/**
* @brief Represents an address to a Host or Peer.
*
* This class provides an abstraction to set an Address and Port to aid
* connection through the Host class, as well as a way to identify a peer
* (not uniquely).
*
* @paragraph Usage
* @code
* // this is for the server, you set a Port to listen on, but not an
* // address to connect to, default address will be to listen on all
* // bindable network interfaces.
* Address address2(1234);
* Host server(address2);
*
* // address to connect to.
* Address address3("localhost", 1234);
* Host client;
* client.connect(address3);
* @endcode
*/
class Address
{
public:
Address() = default;
/**
* @brief Creates an address to bind to.
* @param port The port to listen on.
*
* The usage of this is mainly for something like a server application,
* where there is not an address to connect or listen to, except all
* bindable interfaces. Using this will allow a server to listen for
* data on a specific port - this is useless for a client, since you can
* just use the Host::connect method with the port and host address set
* correctly.
*/
explicit Address(enet_uint16 port);
/**
* @brief Constructs an address to connect to.
* @param host The host that is going to be connected to.
* @param port The port that will be connected to.
*
* The usage of this constructor is more for the client side, where it
* connects to a peer, or rather a Server. This method is not the most
* ideal since it would require previous encoding of an IP or hostname
* to an unsigned integer. The other constructor requiring a string and
* a port are more suited to external use.
*/
Address(enet_uint32 host, enet_uint16 port);
/**
* @brief Constructs a user-friendly address to connect to.
* @param host The hostname or IP that will be connected to.
* @param port The port that will be connected to.
*
* This constructor is made for the user end of the Client connection
* system. An IP such as 127.0.0.1 or 8.8.8.8 (Google) can be used, as
* well as a domain name, such as google.com or similar. The port has
* the same use. The pairing with Host::connect will allow a user to
* connect to any server they wish.
*/
Address(const std::string& host, enet_uint16 port);
// copy constructor & assignment operator.
Address(const ENetAddress& address);
Address& operator=(const ENetAddress& address);
// move constructor & assignment operator.
Address(ENetAddress&& address);
Address& operator=(ENetAddress&& address);
/**
* @brief Sets the host to another value/address.
* @param host The hostname or IP that will be connected to.
*
* This should be preferred over the other setHost, with the signature
* requiring an unsigned integer. That is inconvenient for use and only
* exists for internal purposes.
*/
void setHost(const std::string& host);
/**
* @brief Sets the host to another provided & pre-encoded value/address.
* @param host The host that will be connected to.
*/
void setHost(enet_uint32 host);
/**
* @brief Sets the port to the provided value.
* @param port The port that will be connected to.
*/
void setPort(enet_uint16 port);
/**
* @brief Gets the hostname that will be connected to.
* @return The hostname of the address that will be connected to.
*/
std::string getHostname() const;
/**
* @brief Gets the IP that will be connected to.
* @return The IP of the address that will be connected to.
*/
std::string getIP() const;
/**
* @brief Gets the port that will be connected to.
* @return The port that will be connected to.
*/
enet_uint16 getPort() const;
operator const ENetAddress*() const { return &m_address; }
private:
ENetAddress m_address;
};
} // namespace phx::net

View File

@ -0,0 +1,10 @@
set(currentDir ${CMAKE_CURRENT_LIST_DIR})
set(networkHeaders
${currentDir}/Types.hpp
${currentDir}/Address.hpp
${currentDir}/Peer.hpp
${currentDir}/Packet.hpp
${currentDir}/Host.hpp
PARENT_SCOPE
)

View File

@ -0,0 +1,339 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <Common/Network/Address.hpp>
#include <Common/Network/Packet.hpp>
#include <Common/Network/Peer.hpp>
#include <Common/Network/Types.hpp>
#include <enet/enet.h>
#include <atomic>
#include <functional>
#include <optional>
#include <unordered_map>
namespace phx::net
{
/**
* @brief Represents a host, acting as either a server or a client.
*
* This class provides the functionality for an application to receive,
* connect and send data to peers.
*
* The terminology of "channels" is a system where data can be separated
* when sent and received. This allows for data to be handled separately,
* removing the need for space-wasting headers - this is handled internally.
*
* A note to keep in mind is that the maximum number of peers possible is
* 4096.
*
* @paragraph Usage
* The constructor requiring only the amount of peers and address has been
* setup so the defaults are correct for a client. The only requires a
* single peer (the server) and the address is blank since it will listen to
* all binding interfaces on the computer, for replies from the server.
*
* The other constructor is more ideal for a server, where an address -
* which can be set so only the port is defined and all bindable interfaces
* are listened to - and the amount of peers, alongside the number of
* required channels is provided.
*
* @code
* Host client;
*
* // set the server to listen on port 1234.
* Host server(Address{1234});
*
* Address serverAddress = Address("127.0.0.1", 1234);
* client.connect(serverAddress);
*
* server.onConnect([](Peer& peer, enet_uint32) { std::cout << "Client
* connected from: " << peer.getAddress().getIP(); });
*
* server.onReceive([](Peer& peer, Packet&&, enet_uint32) { std::cout <<
* "Received packet from: " << peer.getAddress().getIP(); });
*
* server.onDisconnect([](std::size_t peerID, enet_uint32) { std::cout <<
* "Client disconnected: " << peerID; });
*
* client.onConnect([](Peer&, enet_uint32) { std::cout << "connected to
* server"; });
*
* client.onReceive([](Peer&, Packet&&, enet_uint8) { std::cout <<
* "received packet from server."; });
*
* client.onDisconnect([](std::size_t peerID, enet_uint32) { std::cout <<
* "Disconnected from server"; });
*
* thread 1:
* while (running) server.poll();
*
* thread 2:
* while (running) client.poll();
* @endcode
*/
class Host
{
public:
using ReceiveCallback =
std::function<void(Peer&, Packet&&, enet_uint32)>;
using ConnectCallback = std::function<void(Peer&, enet_uint32)>;
using DisconnectCallback =
std::function<void(std::size_t, enet_uint32)>;
public:
/**
* @brief Creates a Host with the correct default values for a Client.
* @param peers The amount of peers to allow connections from.
* @param address The address to bind to.
*
* This constructor is ideal for the client - the defaults provided
* allow for 1 peer (the server).
*/
Host(std::size_t peers = 1, const ENetAddress* address = nullptr);
/**
* @brief Creates a Host more suited to a server application.
* @param address The address to bind to - for listening on.
* @param peers The maximum amount of peers that are allowed to connect.
* @param channels The maximum number of channels that can be used.
*
* The address can be an address constructed with only a port. This
* allows for listening on the port on of all bindable interfaces.
* Providing a host/IP will allow you to listen on a specific IP if for
* example, a server has multiple IPs provided to it.
*/
Host(const Address& address, std::size_t peers,
std::size_t channels = 0);
~Host();
// check if value exists before using.
using OptionalPeer = std::optional<std::reference_wrapper<Peer>>;
/**
* @brief Connects to a provided address.
* @param address The address to connect to.
* @return A peer inside an std::optional in case the connection fails.
*
* @paragraph Usage
* You must call the Host::poll function once a connection request has
* been created. The connection data will not be dispatched until poll
* has been called, a recommended example of connecting is shown below:
* @code
* bool connected;
* client.onConnect([&connected](Peer&, enet_uint32) { connected = true;
* });
*
* Peer server;
*
* auto peer = client.connect(Address{ "127.0.0.1", 1234 });
* if (peer)
* {
* // value for the optional, get for the reference_wrapper
* server = peer.value().get();
* }
*
* // poll for 5 seconds.
* client.poll(5000_ms);
*
* if (connected == false)
* {
* // connection failed, try in a loop a few times if you want, but
* // it's already been tried.
* }
* @endcode
*/
OptionalPeer connect(const Address& address);
/**
* @brief Connects to a provided address.
* @param address The address to connect to.
* @param channels The amount of channels to enable on connect.
* @param data User data to send, use only if understood.
* @return A peer inside an std::optional in case the connection fails.
*/
OptionalPeer connect(const Address& address, enet_uint8 channels,
enet_uint32 data = 0);
/**
* @brief Gets the current bandwidth limits (incoming, outgoing).
* @return The current bandwidth limits, in bytes per second.
*/
Bandwidth getBandwidthLimit() const;
/**
* @brief Sets a new bandwidth limit.
* @param bandwidth The bandwidth limits (incoming, outgoing) in bytes
* per second.
*/
void setBandwidthLimit(const Bandwidth& bandwidth);
/**
* @brief Gets the number of currently allocated channels.
* @return The current number of allocated channels.
*/
std::size_t getChannelLimit() const;
/**
* @brief Sets a new limit for the allocated channels.
* @param limit A new limit for number of allocated channels.
*/
void setChannelLimit(std::size_t limit);
/**
* @brief Broadcasts a packet to all connected peers.
* @param packet The packet to send to all peers.
* @param channel The channel to send it on.
*/
void broadcast(Packet& packet, enet_uint8 channel = 0);
void broadcast(Packet&& packet, enet_uint8 channel = 0);
/**
* @brief Sets a callback for when a packet is received.
* @param callback The function to call when a packet is received.
*/
void onReceive(ReceiveCallback callback);
/**
* @brief Sets a callback for when a new connection is established.
* @param callback The function to call when a new peer is connected.
*
* Note: You don't get notified for your outgoing connections, like
* clients to server, but when the server accepts the connection, you
* get notified of a "request" - which is why you have to poll for
* network events once a request has been created.
*/
void onConnect(ConnectCallback callback);
/**
* @brief Sets a callback for when a peer is disconnected.
* @param callback The function to call when a peer is disconnected.
*
* Note: You do get notified when a peer times out, etc...
*/
void onDisconnect(DisconnectCallback callback);
/**
* @brief Polls for any network events that have occurred.
* @param limit The limit of events that should be processed in this
* call.
*
* Note: This function will not wait for events to occur, if there is
* nothing in the queue immediately, this function will just end.
*/
void poll(int limit = 1);
/**
* @brief Polls & waits for any network events that have occurred.
* @param timeout How long to wait for an event for.
* @param limit The maximum amount of events that should be processed.
*/
void poll(time::ms timeout, int limit = 1);
/**
* @brief Flushes all events, sending them off to the respective foreign
* hosts.
*/
void flush();
/**
* @brief Gets the number of connected peers.
* @return The number of connected peers.
*/
std::size_t getPeerCount() const;
/**
* @brief Gets the maximum number of possible connected peers.
* @return The maximum amount of peers that there can be.
*/
std::size_t getPeerLimit() const;
/**
* @brief Gets the address of this host.
* @return The address of this host.
*
* This is for the most part, fairly useless, but it can be used to get
* what port is being used if that data is lost down the line.
*/
const Address& getAddress() const;
/**
* @brief Gets a peer based on its ID.
* @param id The ID of the Peer.
* @return A pointer to the peer, or a nullptr if it doesn't exit.
*/
Peer* getPeer(std::size_t id);
/**
* @brief Gets the total amount of data received.
* @return The total amount of data received in bytes.
*
* Note: This value can overflow. Don't use if not needed.
*/
enet_uint32 getTotalReceievedData() const;
/**
* @brief Gets the total amount of data sent.
* @return The total amount of data sent in bytes.
*
* Note: This value can overflow. Don't use if not needed.
*/
enet_uint32 getTotalSentData() const;
operator ENetHost*() const { return m_host; }
private:
void handleEvent(ENetEvent& event);
Peer& getPeer(ENetPeer& peer);
Peer& createPeer(ENetPeer& peer);
void removePeer(ENetPeer& peer);
friend class Peer;
void disconnectPeer(std::size_t id);
void removePeer(const Peer& peer);
private:
ENetHost* m_host;
Address m_address;
ReceiveCallback m_receiveCallback;
ConnectCallback m_connectCallback;
DisconnectCallback m_disconnectCallback;
std::size_t m_peerID = 0;
std::unordered_map<std::size_t, Peer> m_peers;
static std::atomic<std::size_t> m_activeInstances;
};
} // namespace phx::net

View File

@ -0,0 +1,158 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <Common/EnumTools.hpp>
#include <Common/Network/Types.hpp>
#include <enet/enet.h>
#include <cstddef>
#include <functional>
#include <vector>
namespace phx::net
{
/**
* @brief Flags for deciding Packet flow.
*/
enum class PacketFlags
{
/**
* @brief The packet must use reliable delivery.
*
* A reliable packet is guaranteed to be delivered, and retries will be
* attempted until an acknowledgement (ack) is received. If a certain
* number of retries fail, a disconnection will be assumed and the
* connection will be forcefully reset.
*
* Reliable delivery is slow, if a majority of reliable is required, use
* a TCP library rather than ENet, which is designed with unreliable
* delivery of UDP packets in mind.
*/
RELIABLE = ENET_PACKET_FLAG_RELIABLE,
/**
* @brief The packet will be sent unreliably.
*
* No retry attempts will be made, nor will and acks be received.
*/
UNRELIABLE = ENET_PACKET_FLAG_SENT,
};
/**
* @brief Represents a packet that will be sent to a host/peer.
*/
class Packet
{
public:
using Data = std::vector<std::byte>;
public:
Packet() = default;
/**
* @brief Constructs a packet with some default data.
* @param data The data to send within the packet.
* @param flags The method with which the packet should be sent.
*/
Packet(const Data& data, PacketFlags flags);
/**
* @brief Constructs a packet with a predetermined size.
* @param size The size of the data which will be set later.
* @param flags The method with which the packet should be sent.
*/
Packet(std::size_t size, PacketFlags flags);
// internal use.
Packet(ENetPacket& packet, bool sent);
~Packet();
/**
* @brief Sets the data the packet will have.
* @param data The data to set within the packet.
*
* This method will implicitly resize the packet if required.
*/
void setData(const Data& data);
Packet& operator=(const Data& data);
/// @todo Implement a stream (<<) operator for adding data.
/*
* @brief Gets the data the packet is storing.
* @return The packet's data.
*
* This method is useful on the receiving end. It will return a copy of
* the data since the packet's lifetime cannot be determined.
*/
Data getData() const;
/**
* @brief Resizes the packet.
* @param size The new size for the packet.
*/
void resize(std::size_t size);
/**
* @brief Gets the size of the packet.
* @return The size of the packet.
*/
std::size_t getSize() const;
operator ENetPacket*() const { return m_packet; }
/**
* @brief Prepares a packet for sending.
*
* Note: this currently doesn't do much but set a variable, but this
* variable is important to guarantee that a packet is only destroyed
* once ready. Without this, the destructor may prematurely delete the
* packet's data.
*/
void prepareForSend();
/**
* @brief Checks whether the packet has been sent.
* @return Whether the packet has been sent or not.
*/
bool isSent() const { return m_sent; }
private:
void create(const Data& data, PacketFlags flags);
private:
ENetPacket* m_packet;
bool m_sent = false;
};
} // namespace phx::net
ENABLE_BITWISE_OPERATORS(phx::net::PacketFlags);

View File

@ -0,0 +1,235 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <Common/Network/Address.hpp>
#include <Common/Network/Packet.hpp>
#include <Common/Network/Types.hpp>
#include <enet/enet.h>
#include <chrono>
#include <iostream>
namespace phx::net
{
enum class PeerStatus
{
CONNECTING = ENET_PEER_STATE_CONNECTING,
CONNECTED = ENET_PEER_STATE_CONNECTED,
DISCONNECTING = ENET_PEER_STATE_DISCONNECTING,
DISCONNECTED = ENET_PEER_STATE_DISCONNECTED,
};
class Host;
namespace detail
{
// not for user use
struct PeerData
{
void* data;
std::size_t id;
};
} // namespace detail
/**
* @brief Class to represent a Peer, a foreign host that has been connected
* to.
*
* This class is always instantiated by the Host class, a user must not
* construct a peer that is not default constructed. A default constructed
* peer must still NOT be used, all methods are unsafe until set to a valid
* Peer produced by a Host.
*/
class Peer
{
public:
using Callback = std::function<void(const Packet&, enet_uint8)>;
public:
Peer() = default;
Peer(Host& host, ENetPeer& peer);
Peer& operator=(ENetPeer& peer);
/**
* @brief Disconnects a peer from the host.
* @param data Data to send to the foreign host, use only if understood.
*
* Note: Host::poll must be called if you want a disconnect event to
* propagate. Use cases for this are the client disconnected it's peer
* (the server). The client must poll and wait for a disconnect event
* before quitting.
*/
void disconnect(enet_uint32 data = 0);
/**
* @brief Disconnects a peer from the host IMMEDIATELY.
* @param data Data to send to the foreign host, use only if understood.
*
* Note: This will not produce an event on the host calling this
* function. For example, if the client disconnects it's peer (the
* server) using this method, it will manually have to tell itself that
* it has disconnected, it will not receive an event.
*/
void disconnectImmediately(enet_uint32 data = 0);
/**
* @brief Disconnects a peer from the host once packets are sent.
* @param data Data to send to the foreign host, use only if understood.
*
* This function essentially doesn't do much different from the
* disconnect() method, however it will not read any events queued
* locally for processing, it will send any packets left to send, and
* then ditch any packets that may have been received.
*/
void disconnectOncePacketsAreSent(enet_uint32 data = 0);
/**
* @brief Drops a peer entirely.
*
* Note: this function is not so different from disconnectImmediately().
*/
void drop();
/**
* @brief Pings the host.
*/
void ping() const;
/**
* @brief Gets how long between every automatic ping between peer/host.
* @return The interval between pings.
*/
time::ms getPingInterval() const;
/**
* @brief Sets how long between every automatic ping between peer/host.
* @param interval The interval at which to ping the host.
*/
void setPingInterval(time::ms interval);
/**
* @brief Gets how long it takes for data to go somewhere and come back.
* @return The round trip time, from the peer to the host to the peer.
*/
time::ms getRoundTripTime() const;
/**
* @brief Gets the ratio of packet loss.
* @return The ratio of packet loss.
*/
enet_uint32 getPacketLoss() const;
/**
* @brief Waits to receive a packet from a peer.
* @param callback The callback to use when a packet is received.
*
* Note: this function is useful if you want to filter a peer to receive
* a packet from, useful in the cases of authentication or waiting for
* something.
*/
void receive(Callback callback) const;
/**
* @brief Sends a packet to the peer.
* @param packet The packet to send to the peer.
* @param channel The channel to send it on.
*/
void send(Packet& packet, enet_uint8 channel = 0);
void send(Packet&& packet, enet_uint8 channel = 0);
/**
* @brief Gets the current throttle parameters.
* @return The current throttle parameters.
*
* To understand more about how the throttle works:
* http://enet.bespin.org/group__peer.html#gab35807c848b6c88af12ce8476dffbc84
*/
Throttle getThrottle() const;
/**
* @brief Sets new throttle parameters.
* @param throttle The new throttle parameters.
*
* To understand more about how the throttle works:
* http://enet.bespin.org/group__peer.html#gab35807c848b6c88af12ce8476dffbc84
*/
void setThrottle(const Throttle& throttle);
/**
* @brief Gets the current timeout parameters.
* @return The current parameters for when a timeout event is issued.
*
* Note: The timeout is how long the host will wait before receiving
* confirmation on connection before issuing a disconnect event.
*/
Timeout getTimeout() const;
/**
* @brief Sets new timeout parameters.
* @param timeout The new parameters for when a timeout event is issued.
*
* Note: The timeout is how long the host will wait before receiving
* confirmation on connection before issuing a disconnect event.
*/
void setTimeout(const Timeout& timeout);
/**
* @brief Gets the address of the peer.
* @return The address of the peer.
*/
const Address& getAddress() const;
/**
* @brief The current state of the peer.
* @return Gets the current state of the peer.
*/
PeerStatus getState() const;
/**
* @brief Gets the peer's unique ID.
* @return The unique ID allocated to the peer.
*
* Note: This ID will always be unique, regardless of previous peers -
* this can be used to uniquely identify a peer with a user or something
* similar.
*/
std::size_t getID() const { return std::size_t(m_peer->data); }
operator ENetPeer*() const { return m_peer; }
private:
ENetPeer* m_peer;
Host* m_host;
Address m_address;
};
} // namespace phx::net

View File

@ -0,0 +1,74 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <enet/enet.h>
#include <chrono>
namespace phx
{
namespace time
{
// milliseconds
using ms = std::chrono::duration<unsigned int, std::milli>;
} // namespace time
namespace net
{
struct Timeout
{
time::ms limit;
time::ms minimum;
time::ms maximum;
};
struct Throttle
{
time::ms interval;
enet_uint32 acceleration;
enet_uint32 deceleration;
};
using speed = enet_uint32;
struct Bandwidth
{
// in bytes.
speed incoming;
speed outgoing;
};
} // namespace net
} // namespace phx
// cool postfix operator, you can do 1000_ms instead of time::ms{1000}!
constexpr phx::time::ms operator"" _ms(unsigned long long ms)
{
return phx::time::ms {ms};
}

View File

@ -32,14 +32,21 @@
namespace phx
{
/**
* @brief The positioning for an entity
*/
struct Position
{
/// @brief The direction the entity is facing
math::vec3 rotation;
/// @brief The cardinal position of the entity
math::vec3 position;
};
/**
* @brief The positioning for an entity
*/
struct Position
{
/// @brief The direction the entity is facing
math::vec3 rotation;
/// @brief The cardinal position of the entity
math::vec3 position;
math::vec3 getDirection()
{
return math::vec3 {std::cos(rotation.y) * std::sin(rotation.x),
std::sin(rotation.y),
std::cos(rotation.y) * std::cos(rotation.x)};
};
};
} // namespace phx

View File

@ -0,0 +1,9 @@
set(currentDir ${CMAKE_CURRENT_LIST_DIR})
set(serializationHeaders
${currentDir}/SharedTypes.hpp
${currentDir}/Endian.hpp
${currentDir}/Serializer.hpp
${currentDir}/Serializer.inl
PARENT_SCOPE
)

View File

@ -0,0 +1,284 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <Common/CoreIntrinsics.hpp>
#include <Common/Serialization/SharedTypes.hpp>
#include <cstddef>
#include <cstdint>
// for potential GCC defines.
#include <limits.h>
// by the end of this preprocessor cluster fuck, there should be an
// ENGINE_NATIVE_ENDIAN that is defined. The static assert in the namespace will
// make sure it's not ENGINE_UNKNOWN_ENDIAN, since if we cannot determine the
// platform's endianness we do be in a bit of a pickle.
#define ENGINE_UNKNOWN_ENDIAN -1
#define ENGINE_LITTLE_ENDIAN 0
#define ENGINE_BIG_ENDIAN 1
#define ENGINE_NET_ENDIAN ENGINE_BIG_ENDIAN
// c++17 feature, C++11 extension in CLang, GCC 5+, VS2015+
// you should be fine even without it.
#ifdef __has_include
# if __has_include(<endian.h>)
# include <endian.h> // gnu libc normally provides, linux
# elif __has_include(<machine/endian.h>)
# include <machine/endian.h> //open bsd, macos
# elif __has_include(<sys/param.h>)
# include <sys/param.h> // mingw, some bsd (not open/macos)
# elif __has_include(<sys/isadefs.h>)
# include <sys/isadefs.h> // solaris
# endif
#endif
#if defined(__BIG_ENDIAN__)
# define ENGINE_NATIVE_ENDIAN ENGINE_BIG_ENDIAN
#elif defined(__LITTLE_ENDIAN__)
# define ENGINE_NATIVE_ENDIAN ENGINE_LITTLE_ENDIAN
#endif
// this should exhaust literally every possibility of byte order
#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
# if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \
(defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN) || \
(defined(_BYTE_ORDER) && _BYTE_ORDER == _BIG_ENDIAN) || \
(defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN) || \
(defined(__sun) && defined(__SVR4) && defined(_BIG_ENDIAN)) || \
defined(__ARMEB__) || defined(__THUMBEB__) || \
defined(__AARCH64EB__) || defined(_MIBSEB) || defined(__MIBSEB) || \
defined(__MIBSEB__) || defined(_M_PPC)
# define ENGINE_NATIVE_ENDIAN ENGINE_BIG_ENDIAN
# elif (defined(__BYTE_ORDER__) && \
__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || /* gcc */ \
(defined(__BYTE_ORDER) && \
__BYTE_ORDER == __LITTLE_ENDIAN) /* linux header */ \
|| (defined(_BYTE_ORDER) && _BYTE_ORDER == _LITTLE_ENDIAN) || \
(defined(BYTE_ORDER) && \
BYTE_ORDER == LITTLE_ENDIAN) /* mingw header */ \
|| (defined(__sun) && defined(__SVR4) && \
defined(_LITTLE_ENDIAN)) || /* solaris */ \
defined(__ARMEL__) || \
defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || \
defined(__MIPSEL) || defined(__MIPSEL__) || defined(_M_IX86) || \
defined(_M_X64) || defined(_M_IA64) || /* msvc for intel processors */ \
defined(_M_ARM) /* msvc code on arm executes in little endian mode */
# define ENGINE_NATIVE_ENDIAN ENGINE_LITTLE_ENDIAN
# endif
#endif
// something is wrong, it hasn't been defined yet.
#ifndef ENGINE_NATIVE_ENDIAN
# define ENGINE_NATIVE_ENDIAN ENGINE_UNKNOWN_ENDIAN
# warning \
"Unknown platform endianness. Network/Host byte swaps will not occur - bugs may be present."
#endif
// define builtin byte swapping methods, much faster than custom methods.
// will work for signed as well.
// clang-format off
#if defined(ENGINE_MSVC)
# define byteswap16(x) _byteswap_ushort((x))
# define byteswap32(x) _byteswap_ulong((x))
# define byteswap64(x) _byteswap_uint64((x))
#elif defined(ENGINE_GNUC) && \
((__GNUC__ >= 5) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))
# define byteswap16(x) __builtin_bswap16((x))
# define byteswap32(x) __builtin_bswap32((x))
# define byteswap64(x) __builtin_bswap64((x))
#elif defined(__has_builtin) && __has_builtin(__builtin_bswap64)
# define byteswap16(x) __builtin_bswap16((x))
# define byteswap32(x) __builtin_bswap32((x))
# define byteswap64(x) __builtin_bswap64((x))
#else
// compilers will most likely optimise this out - even if the builtin's don't
// exist.
static inline uint16_t byteswap16(uint16_t x)
{
return (((x >> 8) & 0xffu) | ((x & 0xffu) << 8));
}
static inline uint32_t byteswap32(uint32_t x)
{
return (((x & 0xff000000u) >> 24) | ((x & 0x00ff0000u) >> 8) |
((x & 0x0000ff00u) << 8) | ((x & 0x000000ffu) << 24));
}
static inline uint64_t byteswap64(uint64_t x)
{
return (((x & 0xff00000000000000ull) >> 56) |
((x & 0x00ff000000000000ull) >> 40) |
((x & 0x0000ff0000000000ull) >> 24) |
((x & 0x000000ff00000000ull) >> 8) |
((x & 0x00000000ff000000ull) << 8) |
((x & 0x0000000000ff0000ull) << 24) |
((x & 0x000000000000ff00ull) << 40) |
((x & 0x00000000000000ffull) << 56));
}
#endif
// clang-format on
#include <type_traits>
namespace phx::data::endian
{
/**
* @brief Represents the different Endianness types.
*/
enum class Endian
{
LITTLE = ENGINE_LITTLE_ENDIAN,
BIG = ENGINE_BIG_ENDIAN,
// The de-facto for networking endianness is big endian.
NET = ENGINE_NET_ENDIAN,
// This is the native endianness of the platform, the huge clusterfuck
// of a preprocessor if statement up above is how we figure this out.
NATIVE = ENGINE_NATIVE_ENDIAN
};
namespace detail
{
// struct for internal use dictating whether a piece of data can be
// changed in endianness. this is basically, an PoD and Floating Points.
template <typename T>
struct IsEndianChangable
{
static constexpr bool value = std::is_integral_v<T> || std::is_floating_point_v<T>;
};
// handy template specialized way of handling byteswapping, so we don't
// have to manually write code within the netToHost and hostToNet
// functions at the bottom of this file.
template <std::size_t N>
struct ByteSwapper
{
};
// you don't swap the byte order if there's only one byte.
template <>
struct ByteSwapper<1>
{
template <typename T>
T operator()(const T& t)
{
return t;
}
};
template <>
struct ByteSwapper<2>
{
template <typename T>
T operator()(const T& t)
{
return byteswap16(t);
}
};
template <>
struct ByteSwapper<4>
{
template <typename T>
T operator()(const T& t)
{
return byteswap32(t);
}
// special operator for float since it needs to be turned into an
// integer first.
float operator()(float f)
{
uint64_t t = byteswap32(*reinterpret_cast<const uint32_t*>(&f));
return *reinterpret_cast<const float*>(&t);
}
};
// anything bigger than 8 is bigger than 64bits and if we're there we've
// fucked up already.
template <>
struct ByteSwapper<8>
{
template <typename T>
T operator()(const T& t)
{
return byteswap64(t);
}
// special operator for double since it needs to be turned into an
// integer first.
double operator()(double d)
{
uint64_t t = byteswap64(*reinterpret_cast<const uint64_t*>(&d));
return *reinterpret_cast<const double*>(&t);
}
};
} // namespace detail
/**
* @brief Swaps the endianness of data for sending over a network.
* @tparam T The type of data being converted. (must be integral type)
* @tparam from The endianness that the data currently is.
* @param t The value to check/swap for endianness
* @return The endian-safe value.
*/
template <typename T, Endian from = Endian::NATIVE,
typename U =
std::enable_if_t<detail::IsEndianChangable<T>::value, void>>
T swapForNetwork(const T& t)
{
if constexpr (from != Endian::NET)
{
return detail::ByteSwapper<sizeof(T)>()(t);
}
return t;
}
/**
* @brief Swaps the endianness of data after receiving from a network.
* @tparam T The type of data being converted. (must be integral type)
* @tparam from The endianness that the data currently is.
* @param t The value to check/swap for endianness
* @return The endian-safe value.
*/
template <typename T, Endian from = Endian::NET,
typename U =
std::enable_if_t<detail::IsEndianChangable<T>::value, void>>
T swapForHost(const T& t)
{
if constexpr (from != Endian::NATIVE)
{
return detail::ByteSwapper<sizeof(T)>()(t);
}
return t;
}
} // namespace phx::data::endian

View File

@ -0,0 +1,185 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <Common/Serialization/Endian.hpp>
#include <Common/Serialization/SharedTypes.hpp>
#include <cstddef>
#include <vector>
#include <string>
#include <cstring>
#if defined(__APPLE__)
# define __INT32_EQUAL_LONG__ 1
#elif defined(_WIN32)
# define __INT32_EQUAL_LONG__ 1
#else
# define __INT32_EQUAL_LONG__ 0
#endif
namespace phx
{
class Serializer;
/**
* @brief Interface class for helping with data structures.
*
* This function must be overridden, usage for the Serializer can be found
* below.
*/
class ISerializable
{
public:
virtual Serializer& operator&(Serializer& serializer) = 0;
};
/**
* @brief Serializes data with Endianness correction for network transfer.
*
* This class provides a way to safely accumulate data with automatic
* correction for system endianness for transfer over a network. You can use
* this class on both sides of the system.
*
* @paragraph Usage
* If preparing data to send, the mode used must be Mode::WRITE, since
* you're writing to the buffer. If you've just received data and funneled
* the Packet's data into the serializer, you should use the Mode::READ
* mode. This will prevent you from writing to the buffer (and vice versa
* for the write mode).
*
* To pack a value into the buffer, use the & operator and write mode. The &
* operator is small so you can have a chain of things like: ``serializer &
* var & var2 & var3;``. The buffer can then be retrieved using ``getBuffer()``
*
* To retrieve a value from the buffer, again, use the & operator and read
* mode. You should read **in the same direction of variables than when
* you packed the buffer**. For example, pack the buffer like this:
* @code
* Serializer ser(Serializer::Mode::WRITE);
* ser & var1 & var2 & var3 & var4;
* @endcode
* And unpack the buffer like this:
* @code
* Serializer ser(Serializer::Mode::READ);
* ser & var1 & var2 & var3 & var4;
* @endcode
*
* Usage with a packet:
* @code
* // client:
* int status = 5;
* bool moving = true;
* float wowee = 0.01f;
* std::size_t sequence = 100355;
*
* Serializer ser(Serializer::Mode::WRITE)
* ser & status & moving & wowee & sequence;
*
* send_packet(ser.getBuffer());
*
* // server:
* int status;
* bool moving;
* float wowee;
* std::size_t sequence;
*
* Packet packet = receive_packet();
*
* Serializer ser(Serializer::Mode::READ)
* ser.setBuffer(packet.getData());
* ser & status & moving & wowee & sequence;
*
* // status, moving, wowee and sequence will be equal to their client
* // counterparts.
* @endcode
*/
class Serializer
{
public:
enum class Mode
{
READ,
WRITE
};
public:
explicit Serializer(Mode mode) : m_mode(mode) {}
data::Data& getBuffer() { return m_buffer; }
void setBuffer(std::byte* data, std::size_t dataLength);
void setBuffer(const data::Data& data) { m_buffer = data; }
Serializer& operator&(bool& value);
Serializer& operator&(char& value);
Serializer& operator&(unsigned char& value);
Serializer& operator&(float& value);
Serializer& operator&(double& value);
#if __INT32_EQUAL_LONG__
Serializer& operator&(long& value);
Serializer& operator&(unsigned long& value);
#endif
Serializer& operator&(std::int16_t& value);
Serializer& operator&(std::int32_t& value);
Serializer& operator&(std::int64_t& value);
Serializer& operator&(std::uint16_t& value);
Serializer& operator&(std::uint32_t& value);
Serializer& operator&(std::uint64_t& value);
template <typename T>
Serializer& operator&(std::basic_string<T>& value);
Serializer& operator&(ISerializable& value);
static data::Data end(Serializer& serializer);
private:
template <typename T>
void push(const T& data);
template <typename T>
void push(const std::basic_string<T>& data);
void push(ISerializable& data);
template <typename T>
void pop(T& data);
template <typename T>
void pop(std::basic_string<T>& data);
private:
Mode m_mode;
data::Data m_buffer;
};
} // namespace phx::data
#include <Common/Serialization/Serializer.inl>

View File

@ -0,0 +1,348 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
namespace phx
{
inline void Serializer::setBuffer(std::byte* data, std::size_t dataLength)
{
m_buffer.clear();
m_buffer.insert(m_buffer.begin(), data, data + dataLength);
}
inline Serializer& Serializer::operator&(bool& value)
{
if (m_mode == Mode::READ)
{
pop(value);
}
else
{
push(value);
}
return *this;
}
inline Serializer& Serializer::operator&(char& value)
{
if (m_mode == Mode::READ)
{
pop(value);
}
else
{
push(value);
}
return *this;
}
inline Serializer& Serializer::operator&(unsigned char& value)
{
if (m_mode == Mode::READ)
{
pop(value);
}
else
{
push(value);
}
return *this;
}
inline Serializer& Serializer::operator&(float& value)
{
if (m_mode == Mode::READ)
{
pop(value);
}
else
{
push(value);
}
return *this;
}
inline Serializer& Serializer::operator&(double& value)
{
if (m_mode == Mode::READ)
{
pop(value);
}
else
{
push(value);
}
return *this;
}
#if __INT32_EQUAL_LONG__
inline Serializer& Serializer::operator&(long& value)
{
if (m_mode == Mode::READ)
{
pop(value);
}
else
{
push(value);
}
return *this;
}
inline Serializer& Serializer::operator&(unsigned long& value)
{
if (m_mode == Mode::READ)
{
pop(value);
}
else
{
push(value);
}
return *this;
}
#endif
inline Serializer& Serializer::operator&(std::int16_t& value)
{
if (m_mode == Mode::READ)
{
pop(value);
}
else
{
push(value);
}
return *this;
}
inline Serializer& Serializer::operator&(std::int32_t& value)
{
if (m_mode == Mode::READ)
{
pop(value);
}
else
{
push(value);
}
return *this;
}
inline Serializer& Serializer::operator&(std::int64_t& value)
{
if (m_mode == Mode::READ)
{
pop(value);
}
else
{
push(value);
}
return *this;
}
inline Serializer& Serializer::operator&(std::uint16_t& value)
{
if (m_mode == Mode::READ)
{
pop(value);
}
else
{
push(value);
}
return *this;
}
inline Serializer& Serializer::operator&(std::uint32_t& value)
{
if (m_mode == Mode::READ)
{
pop(value);
}
else
{
push(value);
}
return *this;
}
inline Serializer& Serializer::operator&(std::uint64_t& value)
{
if (m_mode == Mode::READ)
{
pop(value);
}
else
{
push(value);
}
return *this;
}
template <typename T>
inline Serializer& Serializer::operator&(std::basic_string<T>& value)
{
if (m_mode == Mode::READ)
{
pop(value);
}
else
{
push(value);
}
return *this;
}
inline Serializer& Serializer::operator&(ISerializable& value)
{
return value & *this;
}
inline data::Data Serializer::end(Serializer& serializer)
{
return serializer.m_buffer;
}
template <typename T>
void Serializer::push(const T& data)
{
union {
std::byte bytes[sizeof(T)];
T value;
} value;
value.value = data::endian::swapForNetwork(data);
for (std::size_t i = 0; i < sizeof(T); ++i)
{
m_buffer.push_back(value.bytes[i]);
}
}
template <typename T>
void Serializer::push(const std::basic_string<T>& data)
{
// if T is the same as the number of bits in a char, it's a normal
// std::string. This means that there is only 1 byte per character and
// so you don't need to factor in any endianness changes.
if constexpr (sizeof(T) == CHAR_BIT)
{
// this is faster than iterating through every character and
// swapping endianness and essentially doing an unnecessary
// endianness swap.
// push size of string onto data at the end.
// specify unsigned int otherwise it will waste space allocating a
// 64 bit variable.
push(static_cast<unsigned int>(data.length()));
// previous end of the array, so we can append onto that - rather
// than the new end.
const std::size_t prevEnd = m_buffer.size();
// +1 for null terminator.
m_buffer.resize(m_buffer.size() + data.length() + 1);
// convert string to array of std::byte and append to data array.
std::transform(data.begin(), data.end(), m_buffer.begin() + prevEnd,
[](char c) { return std::byte(c); });
}
else
{
// push a new value for each character in the string.
// the reason this exists is because there are different strings in
// the standard library, them being 16bit and 32bit character
// strings.
push(static_cast<unsigned int>(data.length()));
for (auto c : data)
{
push(c);
}
}
}
inline void Serializer::push(ISerializable& data) { data&* this; }
template <typename T>
void Serializer::pop(T& data)
{
union {
std::byte bytes[sizeof(T)];
T value;
} value;
std::memcpy(value.bytes, m_buffer.data(), sizeof(T));
// basically pop front for the amount of bytes of data we're taking.
m_buffer.erase(m_buffer.begin(), m_buffer.begin() + sizeof(T));
data = data::endian::swapForHost(value.value);
}
template <typename T>
void Serializer::pop(std::basic_string<T>& data)
{
if constexpr (sizeof(T) == CHAR_BIT)
{
unsigned int size;
pop(size);
data.resize(size);
std::transform(m_buffer.begin(), m_buffer.begin() + size,
data.begin(),
[](std::byte byte) { return char(byte); });
// basically pop front for the amount of bytes of data we're taking.
m_buffer.erase(m_buffer.begin(), m_buffer.begin() + size);
}
else
{
union {
std::byte bytes[sizeof(T)];
T c;
} values;
unsigned int size;
pop(size);
data.reserve(size);
for (unsigned int i = 0; i < size; ++i)
{
pop(values.c);
data.push_back(values.c);
}
}
}
} // namespace phx

View File

@ -0,0 +1,38 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <cstddef>
#include <vector>
namespace phx::data
{
using Data = std::vector<std::byte>;
using Byte = std::byte;
}

View File

@ -0,0 +1,287 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <queue>
#include <Common/Util/Front.hpp>
namespace phx
{
namespace
{
template <typename T>
class IsValid
{
public:
bool operator()(const T&) { return true; }
};
} // namespace
/**
* @brief A thread safe queue
*
* @tparam T The type of object stored in the queue
* @tparam Validator
* @tparam Container
*/
template <typename T, typename Validator = IsValid<T>,
typename Container = std::queue<T>>
class BlockingQueue : public Container
{
public:
virtual ~BlockingQueue() { stop(); }
void stop()
{
m_done = true;
m_cond.notify_all();
m_mutex.lock();
m_mutex.unlock();
}
void clear()
{
std::lock_guard<std::mutex> lock(m_mutex);
Container::c.clear();
}
/**
* @brief checks if the queue is empty
* @return true if empty
* @return false if not empty
*/
bool empty() const
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_done)
{
return true;
}
bool res = Container::empty();
return res;
}
/**
* @brief Gets the size of the queue
*
* @return How many elements are in the queue
*/
size_t size() const
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_done)
{
return 0;
}
auto res = Container::size();
return res;
}
/**
* @brief Removes an element from the end of the queue.
*
* @return the removed element
*/
T pop()
{
std::unique_lock<std::mutex> lock_cond(m_mutex);
if (m_done)
{
return {};
}
while (true)
{
m_cond.wait(lock_cond,
[this] { return !Container::empty() || m_done; });
if (m_done)
{
return {};
}
T value =
phx::front<Container>(*this); // for std::priority_queue
Container::pop();
if (validator(value))
{
return value;
}
}
}
bool try_pop(T& _value)
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_done)
{
return false;
}
if (Container::empty())
{
return false;
}
_value = std::move(phx::front<Container>(*this));
Container::pop();
return true;
}
/**
* @brief pushes an element to the front of the queue
*
* @param value The element to be pushed to the queue
*/
void push(const T& value)
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_done)
{
return;
}
bool unlock = false;
if (Container::empty())
{
unlock = true;
}
Container::push(value);
if (unlock)
{
m_cond.notify_one();
}
}
void push(T&& value)
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_done)
{
return;
}
bool unlock = false;
if (Container::empty())
{
unlock = true;
}
Container::push(std::move(value));
if (unlock)
{
m_cond.notify_one();
}
}
template <class... Args>
void emplace(Args&&... args)
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_done)
{
return;
}
bool unlock = false;
if (Container::empty())
{
unlock = true;
}
Container::emplace(std::forward(args)...);
if (unlock)
{
m_cond.notify_one();
}
}
public: // STL implementation of constructors
explicit BlockingQueue(const Container& cont) : Container(cont) {}
explicit BlockingQueue(Container&& cont = Container())
: Container(std::move(cont))
{
}
BlockingQueue(const BlockingQueue& other) : Container(other) {}
BlockingQueue(BlockingQueue&& other) : Container(std::move(other)) {}
template <class Alloc>
explicit BlockingQueue(const Alloc& alloc) : Container(alloc)
{
}
template <class Alloc>
BlockingQueue(const Container& cont, const Alloc& alloc)
: Container(cont, alloc)
{
}
template <class Alloc>
BlockingQueue(Container&& cont, const Alloc& alloc)
: Container(std::move(cont), alloc)
{
}
template <class Alloc>
BlockingQueue(const BlockingQueue& other, const Alloc& alloc)
: Container(other, alloc)
{
}
template <class Alloc>
BlockingQueue(BlockingQueue&& other, const Alloc& alloc)
: Container(other, alloc)
{
}
BlockingQueue& operator=(const BlockingQueue& _queue)
{
// std::lock_guard<std::mutex> lock(m_mutex);
return *this;
}
BlockingQueue& operator=(BlockingQueue&& _queue)
{
// std::lock_guard<std::mutex> lock(m_mutex);
// std::lock_guard<std::mutex> lock2(_queue.m_mutex);
return *this;
}
void lock() const { m_mutex.lock(); }
void unlock() const { m_mutex.unlock(); }
protected:
mutable std::mutex m_mutex;
private:
std::condition_variable m_cond;
std::atomic<bool> m_done = false;
Validator validator;
};
template <typename T, typename Validator, class Compare,
typename Container = std::vector<T>>
using PriorityBlockingQueue =
BlockingQueue<T, Validator, std::priority_queue<T, Container, Compare>>;
} // namespace phx

View File

@ -0,0 +1,71 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#pragma once
namespace
{
namespace internal
{
template <class Container>
struct front
{
static auto process(Container& t) { return t.front(); }
};
template <>
struct front<std::string>
{
static auto process(std::string& t) { return t.front(); }
};
template <class T>
struct front<std::queue<T>>
{
static const T& process(std::queue<T>& t) { return t.front(); }
};
template <class T, class... Args>
struct front<std::priority_queue<T, Args...>>
{
static auto process(std::priority_queue<T, Args...>& t)
{
return t.top();
}
};
} // namespace internal
} // namespace
namespace phx
{
template <class Container>
auto front(Container& t)
{
return internal::front<Container>::process(std::forward<Container&>(t));
}
} // namespace phx

View File

@ -79,14 +79,14 @@ namespace phx::voxels
public:
Chunk() = delete;
explicit Chunk(math::vec3 chunkPos);
explicit Chunk(const math::vec3& chunkPos);
~Chunk() = default;
Chunk(const Chunk& other) = default;
Chunk& operator=(const Chunk& other) = default;
Chunk(Chunk&& other) noexcept = default;
Chunk& operator=(Chunk&& other) noexcept = default;
Chunk(math::vec3 chunkPos, const std::string& save);
Chunk(const math::vec3& chunkPos, const std::string& save);
std::string save();

View File

@ -37,11 +37,11 @@ namespace phx::voxels
class Map
{
public:
Map(std::string save, std::string name);
Map(const std::string& save, const std::string& name);
Chunk getChunk(math::vec3 pos);
Chunk getChunk(const math::vec3& pos);
void setBlockAt(math::vec3 pos, BlockType* block);
void save(math::vec3 pos);
void save(const math::vec3& pos);
private:
std::map<math::vec3, Chunk, math::Vector3Key> m_chunks;

View File

@ -0,0 +1,87 @@
// Copyright 2020 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#include <Common/Actor.hpp>
#include <Common/Movement.hpp>
#include <Common/Position.hpp>
using namespace phx;
entt::entity ActorSystem::registerActor(entt::registry* registry)
{
auto entity = registry->create();
registry->emplace<Position>(entity, math::vec3 {0, 0, 0},
math::vec3 {0, 0, 0});
registry->emplace<Movement>(entity, DEFAULT_MOVE_SPEED);
return entity;
}
void ActorSystem::tick(entt::registry* registry, entt::entity entity,
const float dt, InputState input)
{
auto& pos = registry->get<Position>(entity);
/// conversion from 1/1000 of degres to rad
pos.rotation.x = static_cast<float>(input.rotation.x) / 360000.0;
pos.rotation.y = static_cast<float>(input.rotation.y) / 360000.0;
const auto moveSpeed =
static_cast<float>(registry->get<Movement>(entity).moveSpeed);
math::vec3 direction = pos.getDirection();
const math::vec3 right = {std::sin(direction.x - math::PIDIV2), 0.f,
std::cos(direction.x - math::PIDIV2)};
const math::vec3 forward = {std::sin(direction.x), 0.f,
std::cos(direction.x)};
if (input.forward)
{
pos.position += forward * dt * moveSpeed;
}
else if (input.backward)
{
pos.position -= forward * dt * moveSpeed;
}
if (input.left)
{
pos.position -= right * dt * moveSpeed;
}
else if (input.right)
{
pos.position += right * dt * moveSpeed;
}
if (input.up)
{
pos.position.y += dt * moveSpeed;
}
else if (input.down)
{
pos.position.y -= dt * moveSpeed;
}
}

View File

@ -1,16 +1,20 @@
add_subdirectory(Math)
add_subdirectory(Voxels)
add_subdirectory(CMS)
add_subdirectory(Network)
set(currentDir ${CMAKE_CURRENT_LIST_DIR})
set(Sources
${mathSources}
${voxelSources}
${cmsSources}
${networkSources}
${currentDir}/Actor.cpp
${currentDir}/Settings.cpp
${currentDir}/Logger.cpp
${currentDir}/Commander.cpp
${currentDir}/Input.cpp
PARENT_SCOPE
)

View File

@ -0,0 +1,34 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#include <Common/Input.hpp>
phx::Serializer& phx::InputState::operator&(phx::Serializer& this_)
{
return this_ & forward & backward & left & right & up & down & rotation.x & rotation.y & sequence;
}

View File

@ -183,8 +183,9 @@ Logger::Logger(const LoggerConfig& config)
m_file.open(config.logFile);
if (!m_file.is_open())
{
printf("Uh Oh! We couldn't open the log file, guess we won't have any "
"file logging for today. :(\n");
Log message = {LogVerbosity::INFO, "", 0, "LOGGING"};
message << "A log file was not specified, logging only to console.";
log(message);
}
}

View File

@ -0,0 +1,111 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#include <Common/Logger.hpp>
#include <Common/Network/Address.hpp>
using namespace phx::net;
Address::Address(enet_uint16 port) : Address(ENET_HOST_ANY, port) {}
Address::Address(enet_uint32 host, enet_uint16 port)
{
m_address.host = host;
m_address.port = port;
}
Address::Address(const std::string& host, enet_uint16 port)
{
setHost(host);
m_address.port = port;
}
Address::Address(const ENetAddress& address) { m_address = address; }
Address& Address::operator=(const ENetAddress& address)
{
m_address = address;
return *this;
}
Address::Address(ENetAddress&& address) { m_address = address; }
Address& Address::operator=(ENetAddress&& address)
{
m_address = address;
return *this;
}
void Address::setHost(const std::string& host)
{
if (enet_address_set_host(&m_address, host.c_str()))
{
LOG_FATAL("NETCODE") << "Could not resolve hostname.";
}
}
void Address::setHost(enet_uint32 host) { m_address.host = host; }
void Address::setPort(enet_uint16 port) { m_address.port = port; }
std::string Address::getHostname() const
{
// hostnames can be max of 255 bytes.
constexpr int MaxHostNameBytes = 255;
// + 1 for null terminator.
char hostname[MaxHostNameBytes + 1];
if (enet_address_get_host(&m_address, hostname, MaxHostNameBytes))
{
LOG_FATAL("NETCODE") << "Could not resolve hostname.";
}
return hostname;
}
std::string Address::getIP() const
{
// enet doesn't support ipv6 afaik.
constexpr int IPv4MaxBytes = 15;
std::string ip;
// + 1 for null terminator.
char ipRaw[IPv4MaxBytes + 1];
if (enet_address_get_host_ip(&m_address, ipRaw, IPv4MaxBytes))
{
LOG_FATAL("NETCODE") << "Failed to get IP.";
ip = "Unknown IP";
}
else
{
ip = ipRaw;
}
return ip;
}
enet_uint16 Address::getPort() const { return m_address.port; }

View File

@ -0,0 +1,9 @@
set(currentDir ${CMAKE_CURRENT_LIST_DIR})
set(networkSources
${currentDir}/Address.cpp
${currentDir}/Packet.cpp
${currentDir}/Peer.cpp
${currentDir}/Host.cpp
PARENT_SCOPE
)

View File

@ -0,0 +1,247 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#include <Common/Logger.hpp>
#include <Common/Network/Host.hpp>
#include <utility>
using namespace phx::net;
std::atomic<std::size_t> Host::m_activeInstances = 0;
Host::Host(std::size_t peers, const ENetAddress* address)
: Host(address ? Address {*address} : Address {}, peers)
{
}
Host::Host(const Address& address, std::size_t peers, std::size_t channels)
{
if (m_activeInstances == 0)
{
if (enet_initialize())
{
LOG_FATAL("NETCODE") << "Failed to initialize ENet networking.";
exit(EXIT_FAILURE);
}
}
++m_activeInstances;
m_host = enet_host_create(address, peers, channels, 0, 0);
}
Host::~Host()
{
--m_activeInstances;
enet_host_destroy(m_host);
if (m_activeInstances == 0)
{
enet_deinitialize();
}
}
Host::OptionalPeer Host::connect(const Address& address)
{
return connect(address, getChannelLimit());
}
Host::OptionalPeer Host::connect(const Address& address, enet_uint8 channels,
enet_uint32 data)
{
ENetPeer* peer = enet_host_connect(m_host, address, channels, data);
if (!peer)
{
LOG_FATAL("Failed to connect to peer.");
return {};
}
return createPeer(*peer);
}
Bandwidth Host::getBandwidthLimit() const
{
return {m_host->incomingBandwidth, m_host->outgoingBandwidth};
}
void Host::setBandwidthLimit(const Bandwidth& bandwidth)
{
enet_host_bandwidth_limit(m_host, bandwidth.incoming, bandwidth.outgoing);
}
std::size_t Host::getChannelLimit() const { return m_host->channelLimit; }
void Host::setChannelLimit(std::size_t limit)
{
enet_host_channel_limit(m_host, limit);
}
void Host::broadcast(Packet& packet, enet_uint8 channel)
{
packet.prepareForSend();
enet_host_broadcast(m_host, channel, packet);
}
void Host::broadcast(Packet&& packet, enet_uint8 channel)
{
packet.prepareForSend();
enet_host_broadcast(m_host, channel, packet);
}
void Host::onReceive(ReceiveCallback callback)
{
m_receiveCallback = std::move(callback);
}
void Host::onConnect(ConnectCallback callback)
{
m_connectCallback = std::move(callback);
}
void Host::onDisconnect(DisconnectCallback callback)
{
m_disconnectCallback = std::move(callback);
}
void Host::poll(int limit) { poll(0_ms, limit); }
void Host::poll(phx::time::ms timeout, int limit)
{
ENetEvent event;
do
{
if (enet_host_service(m_host, &event, timeout.count()))
{
handleEvent(event);
}
} while (--limit);
}
void Host::flush() { enet_host_flush(m_host); }
std::size_t Host::getPeerCount() const { return m_host->connectedPeers; }
std::size_t Host::getPeerLimit() const { return m_host->peerCount; }
const Address& Host::getAddress() const { return m_address; }
Peer* Host::getPeer(std::size_t id)
{
if (m_peers.find(id) != m_peers.end())
{
return &m_peers[id];
}
return nullptr;
}
enet_uint32 Host::getTotalReceievedData() const
{
return m_host->totalReceivedData;
}
enet_uint32 Host::getTotalSentData() const { return m_host->totalSentData; }
void Host::removePeer(const Peer& peer)
{
removePeer(*static_cast<ENetPeer*>(peer));
}
void Host::handleEvent(ENetEvent& event)
{
ENetPeer* peer = event.peer;
switch (event.type)
{
case ENET_EVENT_TYPE_CONNECT:
if (m_connectCallback)
{
m_connectCallback(createPeer(*peer), event.data);
}
break;
case ENET_EVENT_TYPE_RECEIVE:
if (m_receiveCallback)
{
m_receiveCallback(getPeer(*peer), Packet(*event.packet, true),
event.channelID);
}
enet_packet_destroy(event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
if (m_disconnectCallback)
{
m_disconnectCallback(std::size_t(peer->data), event.data);
}
removePeer(*peer);
break;
default:
break;
}
}
Peer& Host::getPeer(ENetPeer& peer)
{
if (m_peers.find(std::size_t(peer.data)) != m_peers.end())
{
return m_peers.at(std::size_t(peer.data));
}
std::cout << "peer not found";
}
Peer& Host::createPeer(ENetPeer& peer)
{
++m_peerID;
peer.data = reinterpret_cast<void*>(m_peerID);
m_peers.insert({m_peerID, {*this, peer}});
return m_peers.at(m_peerID);
}
void Host::removePeer(ENetPeer& peer)
{
auto id = std::size_t(peer.data);
m_peers.erase(id);
peer.data = nullptr;
}
void Host::disconnectPeer(std::size_t id)
{
if (m_disconnectCallback)
{
m_disconnectCallback(id, 0);
}
removePeer(m_peers.at(id));
}

View File

@ -0,0 +1,121 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#include <Common/Logger.hpp>
#include <Common/Network/Packet.hpp>
using namespace phx::net;
Packet::Packet(const Data& data, PacketFlags flags)
{
// unreliable is fake just cos so removing it.
create(data, flags & ~PacketFlags::UNRELIABLE);
}
Packet::Packet(std::size_t size, PacketFlags flags)
: Packet(*enet_packet_create(
nullptr, size,
static_cast<enet_uint32>(flags & ~PacketFlags::UNRELIABLE)), false)
{
}
Packet::Packet(ENetPacket& packet, bool sent) : m_packet(&packet), m_sent(sent)
{
}
Packet::~Packet()
{
if (!m_sent)
{
enet_packet_destroy(m_packet);
}
}
void Packet::setData(const Data& data)
{
if (m_sent)
{
// packet has already been sent.
LOG_DEBUG("NETCODE") << "Packet has already been sent.";
return;
}
if (data.size() != m_packet->dataLength)
{
enet_packet_resize(m_packet, data.size());
}
for (std::size_t i = 0; i < data.size(); ++i)
{
enet_uint8* ptr = m_packet->data + i;
*ptr = static_cast<enet_uint8>(data[i]);
}
}
Packet& Packet::operator=(const Data& data)
{
if (m_sent)
{
// packet has already been sent.
LOG_DEBUG("NETCODE") << "Packet has already been sent.";
return *this;
}
setData(data);
return *this;
}
Packet::Data Packet::getData() const
{
return {
reinterpret_cast<std::byte*>(m_packet->data),
reinterpret_cast<std::byte*>(m_packet->data + m_packet->dataLength)};
}
void Packet::resize(std::size_t size)
{
if (m_sent)
{
// packet has already been sent.
LOG_DEBUG("NETCODE") << "Packet has already been sent.";
return;
}
enet_packet_resize(m_packet, size);
}
std::size_t Packet::getSize() const { return m_packet->dataLength; }
void Packet::prepareForSend() { m_sent = true; }
void Packet::create(const Data& data, PacketFlags flags)
{
m_packet = enet_packet_create(data.data(), data.size(),
static_cast<enet_uint32>(flags));
}

View File

@ -0,0 +1,141 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#include <Common/Logger.hpp>
#include <Common/Network/Host.hpp>
#include <Common/Network/Peer.hpp>
using namespace phx::net;
Peer::Peer(Host& host, ENetPeer& peer) : m_peer(&peer), m_host(&host) {}
Peer& Peer::operator=(ENetPeer& peer)
{
m_peer = &peer;
m_address = peer.address;
return *this;
}
void Peer::disconnect(enet_uint32 data) { enet_peer_disconnect(m_peer, data); }
void Peer::disconnectImmediately(enet_uint32 data)
{
// doing this doesn't produce a disconnect event on the host, so we manually
// trigger the disconnection callback.
std::size_t id = getID();
enet_peer_disconnect_now(m_peer, data);
m_host->disconnectPeer(id);
}
void Peer::disconnectOncePacketsAreSent(enet_uint32 data)
{
enet_peer_disconnect_later(m_peer, data);
}
void Peer::drop()
{
// doing this doesn't produce a disconnect event on the host, so we manually
// trigger the disconnection callback.
std::size_t id = getID();
enet_peer_reset(m_peer);
m_host->disconnectPeer(id);
}
void Peer::ping() const { enet_peer_ping(m_peer); }
phx::time::ms Peer::getPingInterval() const
{
return phx::time::ms {m_peer->pingInterval};
}
void Peer::setPingInterval(phx::time::ms interval)
{
enet_peer_ping_interval(m_peer, interval.count());
}
phx::time::ms Peer::getRoundTripTime() const
{
return phx::time::ms {m_peer->roundTripTime};
}
enet_uint32 Peer::getPacketLoss() const { return m_peer->packetLoss; }
void Peer::receive(Callback callback) const
{
enet_uint8 channel;
auto packet = enet_peer_receive(m_peer, &channel);
callback(Packet {*packet, true}, channel);
}
void Peer::send(Packet& packet, enet_uint8 channel)
{
packet.prepareForSend();
enet_peer_send(m_peer, channel, packet);
}
void Peer::send(Packet&& packet, enet_uint8 channel)
{
packet.prepareForSend();
enet_peer_send(m_peer, channel, packet);
}
Throttle Peer::getThrottle() const
{
return {phx::time::ms {m_peer->packetThrottleInterval},
m_peer->packetThrottleAcceleration,
m_peer->packetThrottleDeceleration};
}
void Peer::setThrottle(const Throttle& throttle)
{
enet_peer_throttle_configure(m_peer, throttle.interval.count(),
throttle.acceleration, throttle.deceleration);
}
Timeout Peer::getTimeout() const
{
return {
phx::time::ms {m_peer->timeoutLimit},
phx::time::ms {m_peer->timeoutMinimum},
phx::time::ms {m_peer->timeoutMaximum},
};
}
void Peer::setTimeout(const Timeout& timeout)
{
enet_peer_timeout(m_peer, timeout.limit.count(), timeout.minimum.count(),
timeout.maximum.count());
}
const Address& Peer::getAddress() const { return m_address; }
PeerStatus Peer::getState() const
{
return static_cast<PeerStatus>(m_peer->state);
}

View File

@ -32,7 +32,6 @@
#include <cstring>
using namespace phx::voxels;
using namespace phx;
BlockRegistry::BlockRegistry()
{
@ -50,7 +49,7 @@ BlockRegistry::BlockRegistry()
registerBlock(outOfBoundsBlock);
}
void BlockRegistry::registerAPI(cms::ModManager* manager)
void BlockRegistry::registerAPI(phx::cms::ModManager* manager)
{
manager->registerFunction(
"voxel.block.register", [manager](sol::table luaBlock) {

View File

@ -31,14 +31,14 @@
#include <iostream>
using namespace phx::voxels;
using namespace phx;
Chunk::Chunk(math::vec3 chunkPos) : m_pos(chunkPos)
Chunk::Chunk(const phx::math::vec3& chunkPos) : m_pos(chunkPos)
{
m_blocks.reserve(CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH);
}
Chunk::Chunk(math::vec3 chunkPos, const std::string& save) : m_pos(chunkPos)
Chunk::Chunk(const phx::math::vec3& chunkPos, const std::string& save)
: m_pos(chunkPos)
{
std::string_view search = save;
size_t pos;
@ -76,10 +76,10 @@ void Chunk::autoTestFill()
}
}
math::vec3 Chunk::getChunkPos() const { return m_pos; }
phx::math::vec3 Chunk::getChunkPos() const { return m_pos; }
std::vector<BlockType*>& Chunk::getBlocks() { return m_blocks; }
BlockType* Chunk::getBlockAt(math::vec3 position) const
BlockType* Chunk::getBlockAt(phx::math::vec3 position) const
{
if (position.x < CHUNK_WIDTH && position.y < CHUNK_HEIGHT &&
position.z < CHUNK_DEPTH)
@ -91,7 +91,7 @@ BlockType* Chunk::getBlockAt(math::vec3 position) const
1); // 1 is always out of bounds
}
void Chunk::setBlockAt(math::vec3 position, BlockType* newBlock)
void Chunk::setBlockAt(phx::math::vec3 position, BlockType* newBlock)
{
if (position.x < CHUNK_WIDTH && position.y < CHUNK_HEIGHT &&
position.z < CHUNK_DEPTH)
@ -99,4 +99,3 @@ void Chunk::setBlockAt(math::vec3 position, BlockType* newBlock)
m_blocks[getVectorIndex(position)] = newBlock;
}
}

View File

@ -32,14 +32,13 @@
#include <utility>
using namespace phx::voxels;
using namespace phx;
Map::Map(std::string save, std::string name)
: m_save(std::move(save)), m_mapName(std::move(name))
Map::Map(const std::string& save, const std::string& name)
: m_save(save), m_mapName(name)
{
}
Chunk Map::getChunk(math::vec3 pos)
Chunk Map::getChunk(const phx::math::vec3& pos)
{
if (m_chunks.find(pos) != m_chunks.end())
{
@ -117,7 +116,7 @@ void Map::setBlockAt(phx::math::vec3 position, BlockType* block)
save(chunkPosition);
}
void Map::save(phx::math::vec3 pos)
void Map::save(const phx::math::vec3& pos)
{
std::ofstream saveFile;
std::string position = "." + std::to_string(int(pos.x)) + "_" +

View File

@ -9,7 +9,8 @@ function hello (args)
print("with you, the force is not")
end
end
core.command.register("Hello", "Master the arts of the Jedi you must", hello)
--core.command.register("Hello", "Master the arts of the Jedi you must", hello)
local dirtAudioRef = audio.loadMP3("core.dirt_place", "Modules/mod1/Assets/Audio/dirt_place.mp3");

View File

@ -1,6 +1,9 @@
set(currentDir ${CMAKE_CURRENT_LIST_DIR})
set(Headers
${currentDir}/Server.hpp
${currentDir}/Server.hpp
${currentDir}/Iris.hpp
${currentDir}/Game.hpp
${currentDir}/Commander.hpp
PARENT_SCOPE
)
PARENT_SCOPE
)

View File

@ -0,0 +1,126 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
/**
* @file Commander.hpp
* @brief Header file to implement a command execution system designed to
* interface with a terminal.
*
* @copyright Copyright (c) 2019-2020
*
*/
#pragma once
#include <Server/Iris.hpp>
#include <Common/CMS/ModManager.hpp>
#include <entt/entity/registry.hpp>
#include <functional>
#include <sstream>
#include <string>
#include <vector>
namespace phx::server
{
/**
* @brief A function that is called when a command is executed.
*
* The function should take a vector of strings. Each string is an argument
* similar to how programs are called from the terminal.
*
*/
using CommandFunction = std::function<void(std::vector<std::string> args)>;
struct Command
{
std::string command;
std::string help;
CommandFunction callback;
};
/**
* @brief The command book stores commands and information on them to be
* used by a commander.
*/
class Commander
{
public:
explicit Commander(net::Iris* iris);
void registerAPI(cms::ModManager* manager);
/**
* @brief Registers a command in the command registry.
*
* If a command already exists in the registry, this function will
* over write that command with the new data.
*
* @param command The keyword for calling the command.
* @param help A help string that can be displayed to the user.
* @param f The function that is called when the command is executed.
*/
void add(const std::string& command, const std::string& help,
const CommandFunction& f);
/**
* @brief Calls a command.
*
* @param command The keyword for calling the command.
* @param args The arguments to be passed to the command.
* @param userRef The user who ran the command
* @return Returns True if the function was called and False if the
* function could not be found
*/
bool run(std::size_t userRef, const std::string& input);
/**
* @brief Returns helpstring for command.
*
* @param args array of input, args[0] is the command helpstring is
* returned for, all other array values are not used.
* @param userRef The user who ran the command
* @return Returns True if successful and False if it could not find
* the inputted command.
*/
bool help(std::size_t userRef, const std::vector<std::string>& args);
/**
* @brief Outputs a string listing available commands.
*
* @param userRef The user who ran the command
*/
void list(std::size_t userRef);
private:
net::Iris* m_iris;
std::unordered_map<std::string, Command> m_commands;
};
} // namespace phx

View File

@ -0,0 +1,76 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <Server/Commander.hpp>
#include <Server/Iris.hpp>
#include <entt/entt.hpp>
namespace phx::server
{
class Game
{
public:
/** @brief The server side game object, this handles all of the core
* game logic.
*
* @param registry The shared EnTT registry
* @param running Pointer to a boolean, the threaded function only runs
* if this is true
* @param iris Pointer to the nextworking system
*/
Game(entt::registry* registry, bool* running, net::Iris* iris);
/** @brief Loads all API's that the game utilizes into a CMS ModManager
*
* @param manager The mod manager to load the API into
*/
void registerAPI(cms::ModManager* manager);
/**
* @brief Runs the main game loop as long as running is true
*/
void run();
/// @brief Just a temporary static storage for the DT
/// @TODO Move this to a config file
static constexpr float dt = 1.f / 20.f;
private:
/// @brief The main loop runs while this is true
bool* m_running;
/// @breif An EnTT registry to store various data in
entt::registry* m_registry;
/// @brief The networking object to get data from
net::Iris* m_iris;
/// @brief A commander object to process commands
Commander* m_commander;
};
} // namespace phx::server

View File

@ -0,0 +1,161 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#pragma once
// This is needed because Windows https://github.com/skypjack/entt/issues/96
#ifndef NOMINMAX
# define NOMINMAX
#endif
#include <Common/Input.hpp>
#include <Common/Network/Host.hpp>
#include <Common/Util/BlockingQueue.hpp>
#include <enet/enet.h>
#include <entt/entt.hpp>
namespace phx::server::net
{
struct StateBundle
{
bool ready;
std::size_t users;
std::size_t sequence;
std::unordered_map<entt::entity, InputState> states;
};
struct MessageBundle
{
size_t userID;
std::string message;
};
class Iris
{
public:
/**
* @brief Creates a networking object to handle listening for packets
*
* @param registry The shared EnTT registry
* @param running Pointer to a boolean, the threaded function only runs
* if this is true
*/
Iris(entt::registry* registry);
/**
* @brief Cleans up any internal only objects
*/
~Iris();
/**
* @brief Loops listening to the netowork and populates queues for data
* consumption
*/
void run();
void kill() { m_running = false; };
/**
* @brief Actions taken when a user disconnects
*
* @param userRef The user who disconnected
*/
void disconnect(std::size_t peerID);
/**
* @brief Actions taken when an event is received
*
* @param userRef The user who sent the event packet
* @param data The data in the event packet
* @param dataLength The length of the data in the event packet
*/
void parseEvent(std::size_t userID, phx::net::Packet& packet);
/**
* @brief Actions taken when a state is received
*
* @param userRef The user who sent the state packet
* @param data The data in the state packet
* @param dataLength The length of the data in the state packet
*/
void parseState(std::size_t userID, phx::net::Packet& packet);
/**
* @brief Actions taken when a message is received
*
* @param userRef The user who sent the message packet
* @param data The data in the message packet
* @param dataLength The length of the data in the message packet
*/
void parseMessage(std::size_t userID, phx::net::Packet& packet);
/**
* @brief Sends an event packet to a client
*
* @param userRef The user to sent the event to
* @param data The event packet data
*/
void sendEvent(std::size_t userID, enet_uint8* data);
/**
* @brief Sends a state packet to a client
*
* @param userRef The user to sent the state to
* @param data The state packet data
*/
void sendState(entt::registry* registry, std::size_t sequence);
/**
* @brief Sends a message packet to a client
*
* @note Const ref's are not used here due to the nature of the
* serializer
*
* @param userRef The user to sent the message to
* @param data The message packet data
*/
void sendMessage(std::size_t userID, std::string message);
/**
* @brief The Queue of bundled states received
*/
std::vector<StateBundle> currentBundles;
BlockingQueue<StateBundle> stateQueue;
/**
* @brief The Queue of messages received
*/
BlockingQueue<MessageBundle> messageQueue;
private:
bool m_running;
phx::net::Host* m_server;
entt::registry* m_registry;
std::unordered_map<std::size_t, entt::entity> m_users;
};
} // namespace phx::server::net

View File

@ -28,18 +28,53 @@
#pragma once
#include <Server/Game.hpp>
#include <Server/Iris.hpp>
#include <Server/User.hpp>
#include <Common/CMS/ModManager.hpp>
#include <entt/entt.hpp>
#include <enet/enet.h>
#include <array>
#include <string>
namespace phx::server
{
class Server
{
public:
Server() = default;
~Server() = default;
/**
* @brief Core object for the server
*
* @param save The save we are loading
*/
Server(std::string save);
~Server();
/// @brief Main loop for the server
void run();
private:
bool m_running;
/// @brief central boolean to control if the game is running or not
bool m_running = true;
/// @breif An EnTT registry to store various data in
entt::registry m_registry;
/// @brief The networking object, this listens for incoming data
net::Iris* m_iris;
/** @brief @brief The server side game object, this handles all of the
* core game logic.
*/
Game* m_game;
/// @brief The mod manager providing LUA API functionality
cms::ModManager* m_modManager;
/// The name of the save we are running
std::string m_save;
};
} // namespace phx::server

View File

@ -0,0 +1,47 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <enet/enet.h>
#include <entt/entt.hpp>
#include <string>
namespace phx::server
{
struct User
{
std::string userName;
ENetPeer* peer;
};
struct Player
{
entt::entity actor;
};
} // namespace phx::server

View File

@ -1,8 +1,11 @@
set(currentDir ${CMAKE_CURRENT_LIST_DIR})
set(Sources
${currentDir}/Server.cpp
${currentDir}/Server.cpp
${currentDir}/Iris.cpp
${currentDir}/Game.cpp
${currentDir}/Commander.cpp
${currentDir}/Main.cpp
${currentDir}/Main.cpp
PARENT_SCOPE
)
PARENT_SCOPE
)

View File

@ -0,0 +1,212 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
/**
* @file Commander.cpp
* @brief Source file to implement a command execution system designed to
* interface with a terminal.
*
* @copyright Copyright (c) 2019-2020
*
*/
#include <Common/Logger.hpp>
#include <Server/Commander.hpp>
using namespace phx::server;
Commander::Commander(net::Iris* iris) : m_iris(iris) {}
void Commander::registerAPI(phx::cms::ModManager* manager)
{
/**
* @addtogroup luaapi
*
* @subsubsection corecmdreg core.command.register
* @brief Registers a new command
*
* In the terminal typing "/" followed by a command will execute
the *command
*
* @param command The command to register
* @param help A helpstring that is printed to terminal when
typing
*"/help <command>"
* @param f The callback function that is called by the commander
* The callback function must take a table as an argument
* Any arguments included when the command is executed will be
passed in *this table
*
* @b Example:
* @code {.lua}
* function hello (args)
* if args[1] == "there" then
* print("General Kenobi")
* elseif args[1] == "world" then
* print("World says hi")
* else
* print("with you, the force is not")
* end
* end
* core.command.register("Hello", "Master the arts of the Jedi you
*must", hello)
* @endcode
*/
manager->registerFunction(
"core.command.register",
[this](std::string command, std::string help, sol::function f) {
this->add(command, help, f);
});
}
void Commander::add(const std::string& command, const std::string& help,
const CommandFunction& f)
{
if (m_commands.find(command) != m_commands.end())
{
LOG_INFO("COMMANDER") << "Command overwritten: " << command;
}
m_commands[command] = {command, help, f};
}
bool Commander::run(std::size_t userRef, const std::string& input)
{
// Break the command into args
std::string_view search = input;
std::string command;
std::string arg; // Just used to copy the args out of the search string.
std::vector<std::string> args;
size_t searchLoc;
size_t spaceLoc;
if (!search.empty() && search[0] == '/')
{
// @TODO Can't enter \t or \n rn, might be a good idea to sanitize later
// Search `first_of` initially, it's faster when there's no spaces.
spaceLoc = search.find_first_of(' ');
// if we don't have arguments don't try and populate the args array.
if (spaceLoc != std::string_view::npos)
{
command = search.substr(1, spaceLoc - 1);
search.remove_prefix(spaceLoc);
while ((searchLoc = search.find_first_not_of(' ')) !=
std::string_view::npos)
{
search.remove_prefix(searchLoc); // strip the leading whitespace
spaceLoc = search.find_first_of(' ');
if (spaceLoc != std::string_view::npos)
{
arg =
search.substr(0, spaceLoc); // gen new string from view
args.push_back(arg);
}
// Don't forget to double check if spaceLoc is npos before we
// remove_prefix, otherwise we might end up out of bounds...
else
{
arg = search.substr(0, search.length());
args.push_back(arg);
break;
}
search.remove_prefix(spaceLoc);
}
}
else
{
// otherwise just use the whole string without the command char.
command = search.substr(1, search.length());
}
}
// Check for built in functions
if (command == "help")
{
return help(userRef, args);
}
else if (command == "list")
{
list(userRef);
return true;
}
// If no built in functions match, search library
auto com = m_commands.find(command);
if (com != m_commands.end())
{
com->second.callback(args);
return true;
}
// No commands match
return false;
}
bool Commander::help(std::size_t userRef, const std::vector<std::string>& args)
{
if (args.empty())
{
m_iris->sendMessage(
userRef, "Type /help [command] to learn more about a command "
"\nType /list for a list of available commands\n");
return true;
}
else if (args[0] == "help")
{
m_iris->sendMessage(
userRef, "Type /help [command] to learn more about a command \n");
return true;
}
else if (args[0] == "list")
{
m_iris->sendMessage(userRef, "Lists available commands\n");
return true;
}
auto com = m_commands.find(args[0]);
if (com != m_commands.end())
{
m_iris->sendMessage(userRef, com->second.help + "\n");
return true;
}
m_iris->sendMessage(userRef, "Command \"" + args[0] + "\" not found \n");
return false;
}
void Commander::list(std::size_t userRef)
{
m_iris->sendMessage(userRef, "Available commands:\n");
for (const auto& com : m_commands)
{
m_iris->sendMessage(userRef, "- " + com.second.command + "\n");
}
}

View File

@ -0,0 +1,84 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#include <Server/Game.hpp>
#include <Server/User.hpp>
#include <Common/Actor.hpp>
#include <Common/Movement.hpp>
#include <Common/Position.hpp>
#include <thread>
using namespace phx;
using namespace phx::server;
Game::Game(entt::registry* registry, bool* running, net::Iris* iris)
: m_registry(registry), m_running(running), m_iris(iris)
{
m_commander = new Commander(m_iris);
}
void Game::registerAPI(cms::ModManager* manager)
{
m_commander->registerAPI(manager);
}
void Game::run()
{
while (m_running)
{
// Process everybody's input first
net::StateBundle m_currentState = m_iris->stateQueue.pop();
for (const auto& state : m_currentState.states)
{
ActorSystem::tick(m_registry,
m_registry->get<Player>(state.first).actor, dt,
state.second);
// @todo remove this debug statement before merging to develop
// std::cout << m_registry
// ->get<Position>(
// m_registry->get<Player>(state.first).actor)
// .position
// << "\n";
}
// Process events second
// Process messages last
size_t size = m_iris->messageQueue.size();
for (size_t i = 0; i < size; i++)
{
net::MessageBundle message = m_iris->messageQueue.front();
m_commander->run(message.userID, message.message);
m_iris->messageQueue.pop();
}
m_iris->sendState(m_registry, m_currentState.sequence);
}
}

View File

@ -0,0 +1,239 @@
// Copyright 2019-20 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#include <Server/Iris.hpp>
#include <Server/User.hpp>
#include <Common/Actor.hpp>
#include <Common/Logger.hpp>
#include <Common/Movement.hpp>
#include <Common/Position.hpp>
#include <Common/Serialization/Serializer.hpp>
#include <cstring> //For std::memcpy on non-mac machines
using namespace phx;
using namespace phx::net;
using namespace phx::server::net;
/// @todo Replace this with the config system
static const std::size_t MAX_USERS = 32;
Iris::Iris(entt::registry* registry) : m_registry(registry)
{
m_server = new phx::net::Host(phx::net::Address(7777), MAX_USERS, 3);
m_server->onConnect([this](Peer& peer, enet_uint32) {
LOG_INFO("NETWORK")
<< "Client connected from: " << peer.getAddress().getIP();
{
auto entity = m_registry->create();
m_registry->emplace<Player>(entity,
ActorSystem::registerActor(m_registry));
m_users.emplace(peer.getID(), entity);
}
});
m_server->onReceive(
[this](Peer& peer, Packet&& packet, enet_uint32 channelID) {
switch (channelID)
{
case 0:
parseEvent(peer.getID(), packet);
break;
case 1:
parseState(peer.getID(), packet);
break;
case 2:
parseMessage(peer.getID(), packet);
break;
}
});
m_server->onDisconnect(
[this](std::size_t peerID, enet_uint32) { disconnect(peerID); });
}
Iris::~Iris() { delete m_server; }
void Iris::run()
{
m_running = true;
while (m_running)
{
m_server->poll();
}
}
void Iris::disconnect(std::size_t peerID)
{
LOG_INFO("NETWORK") << peerID << " disconnected";
}
void Iris::parseEvent(std::size_t userID, Packet& packet)
{
std::string data;
phx::Serializer ser(Serializer::Mode::READ);
ser.setBuffer(packet.getData());
ser& data;
printf("Event received");
printf("An Event packet containing %s was received from %lu\n",
data.c_str(), userID);
}
void Iris::parseState(std::size_t userID, phx::net::Packet& packet)
{
const float dt = 1.f / 20.f;
InputState input;
auto data = packet.getData();
phx::Serializer ser(Serializer::Mode::READ);
ser.setBuffer(reinterpret_cast<std::byte*>(&data), packet.getSize());
ser& input;
// If the queue is empty we need to add a new bundle
if (currentBundles.empty())
{
StateBundle bundle;
bundle.sequence = input.sequence;
bundle.ready = false;
bundle.users = 1; ///@todo We need to capture how many users we are
/// expecting packets from
currentBundles.push_back(bundle);
}
// Discard state if its older that the oldest stateBundle
if (input.sequence < currentBundles.front().sequence &&
currentBundles.back().sequence - input.sequence < 10)
{
printf("discard %lu \n", input.sequence);
return;
}
// Fill the stateBundles up to the current input sequence
while ((input.sequence > currentBundles.back().sequence &&
input.sequence - currentBundles.back().sequence > 10) ||
currentBundles.back().sequence == 255)
{
// Insert a new bundle if this is the first packet in this sequence
StateBundle bundle;
bundle.sequence = currentBundles.back().sequence + 1;
bundle.ready = false;
bundle.users = 1; ///@todo We need to capture how many users we are
/// expecting packets from
currentBundles.push_back(bundle);
}
{
// printf("insert existing %lu \n", input.sequence);
for (auto it = currentBundles.begin(); it != currentBundles.end(); ++it)
{
StateBundle& bundle = *it;
if (bundle.sequence == input.sequence)
{
// Thread safety! If we said a bundle is ready, were too late
if (!bundle.ready)
{
bundle.states[m_users[userID]] = input;
// If we have all the states we need, then the bundle is
// ready
if (bundle.states.size() >= bundle.users)
{
bundle.ready = true;
stateQueue.push(bundle);
currentBundles.erase(it);
}
}
break;
}
}
}
// If we have more than 10 states enqueued, assume we lost a packet
if (currentBundles.size() > 10)
{
currentBundles.front().ready = true;
stateQueue.push(currentBundles.front());
currentBundles.erase(currentBundles.begin());
}
}
void Iris::parseMessage(std::size_t userID, phx::net::Packet& packet)
{
std::string input;
auto data = packet.getData();
phx::Serializer ser(Serializer::Mode::READ);
ser.setBuffer(reinterpret_cast<std::byte*>(data.data()), data.size());
ser& input;
/// @TODO replace userID with userName
std::cout << userID << ": " << input << "\n";
if (input[0] == '/')
{
MessageBundle message;
message.message = input.substr(1);
message.userID = userID;
messageQueue.push(message);
}
else
{
sendMessage(userID, input);
}
}
void Iris::sendEvent(std::size_t userID, enet_uint8* data) {}
void Iris::sendState(entt::registry* registry, std::size_t sequence)
{
auto view = registry->view<Position, Movement>();
Serializer ser(Serializer::Mode::WRITE);
ser& sequence;
for (auto entity : view)
{
auto pos = view.get<Position>(entity);
ser& pos.position.x& pos.position.y& pos.position.z;
}
Packet packet = Packet(ser.getBuffer(), PacketFlags::UNRELIABLE);
m_server->broadcast(packet, 1);
}
void Iris::sendMessage(std::size_t userID, std::string message)
{
Serializer ser(Serializer::Mode::WRITE);
ser& message;
Packet packet = Packet(ser.getBuffer(), PacketFlags::RELIABLE);
Peer* peer = m_server->getPeer(userID);
peer->send(packet, 2);
}

View File

@ -33,9 +33,15 @@ using namespace phx;
#undef main
int main(int argc, char** argv)
{
server::Server* server = new server::Server();
// std::string save;
// if (argc > 0){
// save = argv[0];
// } else {
// save = "save1";
// }
server::Server* server = new server::Server("save1");
server->run();
return 0;
}

View File

@ -1,4 +1,4 @@
// Copyright 2019-20 Genten Studios
// Copyright 2020 Genten Studios
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
@ -27,25 +27,135 @@
// POSSIBILITY OF SUCH DAMAGE.
#include <Server/Server.hpp>
#include <Common/Voxels/BlockRegistry.hpp>
#include <Common/Logger.hpp>
#include <Common/Settings.hpp>
#include <iostream>
#include <thread>
#include <utility>
using namespace phx::server;
using namespace phx;
void Server::run() {
std::cout << "Hello, Server!\nType \"exit\" to exit" << std::endl;
Settings::get()->load("config.txt");
m_running = true;
while(m_running == true){
std::cout << "\n>";
std::string input;
std::cin >> input;
if (input == "exit"){
m_running = false;
}
std::cout << input;
}
Settings::get()->save("config.txt");
}
Server::Server(std::string save) : m_save(std::move(save))
{
m_iris = new server::net::Iris(&m_registry);
m_game = new Game(&m_registry, &m_running, m_iris);
}
void registerUnusedAPI(cms::ModManager* manager)
{
manager->registerFunction("core.input.registerInput",
[](std::string uniqueName,
std::string displayName,
std::string defaultKey) {});
manager->registerFunction("core.input.getInput", [](int input) {});
manager->registerFunction("core.input.getInputRef",
[](std::string uniqueName) {});
manager->registerFunction("core.input.registerCallback",
[](int input, sol::function f) {});
manager->registerFunction(
"audio.loadMP3",
[=](const std::string& uniqueName, const std::string& filePath) {});
manager->registerFunction("audio.play", [=](sol::table source) {});
}
void Server::run()
{
std::cout << "Hello, Server!" << std::endl;
Logger::get()->initialize({});
Settings::get()->load("config.txt");
// Initialize the Modules //
std::fstream fileStream;
std::vector<std::string> toLoad;
fileStream.open("Saves/" + m_save + "/Mods.txt");
if (!fileStream.is_open())
{
LOG_FATAL("CMS") << "Error opening save file: \"Saves/" + m_save +
"/Mods.txt\"";
exit(EXIT_FAILURE);
}
std::string modules;
while (std::getline(fileStream, modules))
{
toLoad.push_back(modules);
}
m_modManager = new cms::ModManager(toLoad, {"Modules"});
m_modManager->registerFunction("core.print", [=](const std::string& text) {
std::cout << text << "\n";
});
voxels::BlockRegistry::get()->registerAPI(m_modManager);
Settings::get()->registerAPI(m_modManager);
m_game->registerAPI(m_modManager);
m_modManager->registerFunction("core.log_warning", [](std::string message) {
LOG_WARNING("MODULE") << message;
});
m_modManager->registerFunction("core.log_fatal", [](std::string message) {
LOG_FATAL("MODULE") << message;
});
m_modManager->registerFunction("core.log_info", [](std::string message) {
LOG_INFO("MODULE") << message;
});
m_modManager->registerFunction("core.log_debug", [](std::string message) {
LOG_DEBUG("MODULE") << message;
});
registerUnusedAPI(m_modManager);
float progress = 0.f;
auto result = m_modManager->load(&progress);
if (!result.ok)
{
LOG_FATAL("CMS") << "An error has occurred loading modules.";
exit(EXIT_FAILURE);
}
// Modules Initialized //
// Fire up Threads //
m_running = true;
std::thread t_iris(&server::net::Iris::run, m_iris);
std::thread t_game(&Game::run, m_game);
// Enter Main Loop //
std::string input;
while (m_running)
{
/// @todo Replace simple loop with commander
std::cin >> input;
if (input == "q")
{
m_running = false;
m_iris->kill();
}
}
// Begin Shutdown //
t_iris.join();
t_game.join();
Settings::get()->save("config.txt");
}
Server::~Server()
{
delete m_iris;
delete m_game;
delete m_modManager;
}

View File

@ -15,21 +15,29 @@ set(ALSOFT_NO_CONFIG_UTIL ON)
set(ALSOFT_EXAMPLES OFF)
set(ALSOFT_TESTS OFF)
# turn off the EnTT tests.
set(BUILD_TESTING OFF)
# define for EnTT to work
add_definitions(-DNOMINMAX)
add_subdirectory (openal-soft)
add_subdirectory (SDL2)
add_subdirectory (Glad)
add_subdirectory (ImGui)
add_subdirectory (sol2)
add_subdirectory (lua)
add_subdirectory (enet)
add_subdirectory (entt)
add_subdirectory (json)
# new line for each group of dependencies. Currently SDL, OpenGL, Lua and then OpenAL.
set_target_properties(SDL2main SDL2-static uninstall
Glad ImGui
lua liblua
build_version native-tools
OpenAL common ex-common
PROPERTIES FOLDER Dependencies)
Glad ImGui
lua liblua
enet aob build_version native-tools
OpenAL common ex-common
PROPERTIES FOLDER Dependencies)
# utility thing to make phoenix cmake tidier
set(PHX_THIRD_PARTY_INCLUDES
@ -38,6 +46,7 @@ set(PHX_THIRD_PARTY_INCLUDES
${CMAKE_CURRENT_LIST_DIR}/ImGui
${CMAKE_CURRENT_LIST_DIR}/ImGui/examples
${CMAKE_CURRENT_LIST_DIR}/lua
${CMAKE_CURRENT_LIST_DIR}/enet/include
${CMAKE_CURRENT_LIST_DIR}/sol2/include
${CMAKE_CURRENT_LIST_DIR}/stb_image
${CMAKE_CURRENT_LIST_DIR}/stb_rectpack
@ -54,11 +63,14 @@ set(PHX_THIRD_PARTY_LIBRARIES
SDL2main
Glad
ImGui
enet
sol2
liblua
nlohmann_json::nlohmann_json
OpenAL
$<$<PLATFORM_ID:Windows>:opengl32.lib> # link to opengl32.lib if windows.
$<$<PLATFORM_ID:Windows>:ws2_32.lib> # link to ws2_32.lib if windows.
$<$<PLATFORM_ID:Windows>:winmm.lib> # link to winmm.lib if windows.
PARENT_SCOPE
)

1
Phoenix/ThirdParty/enet vendored Submodule

@ -0,0 +1 @@
Subproject commit 6537dc81f36ff9c8d5c5a0f53950e4d39b374475