Initial commit.

This commit is contained in:
Signal 2026-02-22 17:45:44 -05:00
commit f215bc3742
43 changed files with 2983 additions and 0 deletions

6
Shared/CMakeLists.txt Normal file
View file

@ -0,0 +1,6 @@
file(GLOB_RECURSE MY_SOURCES CONFIGURE_DEPENDS
"*.cpp"
"*.h"
)
add_library(Shared ${MY_SOURCES})

91
Shared/Events.h Normal file
View file

@ -0,0 +1,91 @@
#pragma once
#include <unordered_map>
#include <vector>
#include <functional>
#include <typeindex>
#include <any>
#include <algorithm>
class EventTarget {
public:
template<typename T>
using Callback = std::function<void(const T &)>;
// Subscribe to an event type.
template<typename T>
uint64_t listen(Callback<T> callback) {
auto type = std::type_index(typeid(T));
auto & vec = callbacks[type];
uint64_t id = nextEventId++;
vec.emplace_back([cb = std::move(callback)](const std::any & e) {
cb(std::any_cast<const T &>(e));
});
return id;
}
// Unsubscribe a specific listener.
template<typename T>
void unlisten(uint64_t id) {
_unlisten(std::type_index(typeid(T)), id);
}
// Synchronously dispatch an event.
template<typename T>
void dispatch(const T & event) {
auto it = callbacks.find(std::type_index(typeid(T)));
if (it == callbacks.end()) return;
// Copy the list in case a callback unsubscribes during dispatch.
auto copy = it->second;
for (const auto & cb : copy) {
cb(event);
}
}
// Should be called on a main loop to dispatch queued events.
void processQueue() {
for (auto & [anyEvent, copy] : queued) {
auto it = callbacks.find(anyEvent.type());
if (it != callbacks.end()) {
for (const auto & cb : copy) {
cb(anyEvent);
}
}
}
queued.clear();
}
// Queue an event to be dispatched on the next call to `processQueue`.
template<typename T>
void queue(const T & event) {
auto type = std::type_index(typeid(T));
auto it = callbacks.find(type);
if (it == callbacks.end()) return;
queued.emplace_back(std::any(event), it->second);
}
private:
using AnyCallback = std::function<void(const std::any &)>;
using CallbackWithId = std::pair<AnyCallback, uint64_t>;
std::unordered_map<std::type_index, std::vector<AnyCallback>> callbacks {};
std::vector<std::pair<std::any, std::vector<AnyCallback>>> queued {};
uint64_t nextEventId = 0;
void _unlisten(std::type_index type, uint64_t id) {
// auto it = callbacks.find(type);
// if (it == callbacks.end()) return;
//
// auto & vec = it->second;
// auto erase_it = std::find_if(vec.begin(), vec.end(),
// [id](const auto& p) { return p.second == id; });
//
// if (erase_it != vec.end()) {
// *erase_it = std::move(vec.back());
// vec.pop_back();
// }
}
};

View file

@ -0,0 +1,148 @@
#include <string>
#include <thread>
#include <ngtcp2/ngtcp2_crypto.h>
#include <ngtcp2/ngtcp2_crypto_gnutls.h>
#include <gnutls/crypto.h>
#include <cista.h>
#include "Connection.h"
namespace Artifact {
Connection::Connection() {
ngtcp2_settings_default(&settings);
ngtcp2_transport_params_default(&transportParams);
transportParams.initial_max_streams_bidi = 1;
transportParams.initial_max_data = 65535;
transportParams.initial_max_stream_data_bidi_local = 65535;
callbacks.encrypt = ngtcp2_crypto_encrypt_cb;
callbacks.decrypt = ngtcp2_crypto_decrypt_cb;
callbacks.hp_mask = ngtcp2_crypto_hp_mask_cb;
callbacks.update_key = ngtcp2_crypto_update_key_cb;
callbacks.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb;
callbacks.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb;
callbacks.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb;
callbacks.rand = [](auto dest, auto len, auto ctx) {
gnutls_rnd(GNUTLS_RND_RANDOM, dest, len);
};
callbacks.get_new_connection_id = [](auto conn, auto cid, auto token, auto cidlen, auto userdata) {
gnutls_rnd(GNUTLS_RND_RANDOM, cid, cidlen);
gnutls_rnd(GNUTLS_RND_RANDOM, token, NGTCP2_STATELESS_RESET_TOKENLEN);
return 0;
};
callbacks.version_negotiation = ngtcp2_crypto_version_negotiation_cb;
gnutls_rnd(GNUTLS_RND_RANDOM, dcid.data, dcid.datalen);
gnutls_rnd(GNUTLS_RND_RANDOM, scid.data, scid.datalen);
}
void readPacket(ngtcp2_pkt_info * info, void * packet) {
}
ClientConnection::ClientConnection(std::string host, std::string port) {
struct addrinfo hints;
struct addrinfo * local = nullptr;
struct addrinfo * remote = nullptr;
getaddrinfo("::", "", &hints, &local);
getaddrinfo(host.c_str(), port.c_str(), &hints, &remote);
ngtcp2_path path = {
.local = {
.addr = local->ai_addr,
.addrlen = local->ai_addrlen
},
.remote = {
.addr = remote->ai_addr,
.addrlen = remote->ai_addrlen
}
};
if (gnutls_init(&session, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS) {
throw std::runtime_error("Failed to initialize gnutls.");
}
gnutls_set_default_priority(session);
ngtcp2_conn_client_new(&conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_V1, &callbacks, &settings, &transportParams, nullptr, this);
ngtcp2_conn_set_tls_native_handle(conn, session);
}
ServerConnection::ServerConnection(ngtcp2_path path) : path(path) {
if (gnutls_init(&session, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS) {
throw std::runtime_error("Failed to initialize gnutls.");
}
gnutls_set_default_priority(session);
ngtcp2_conn_server_new(&conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_V1, &callbacks, &settings, &transportParams, nullptr, this);
ngtcp2_conn_set_tls_native_handle(conn, session);
}
ServerListener::ServerListener(std::string port) {
struct addrinfo hints;
struct addrinfo * local = nullptr;
getaddrinfo("::", "", &hints, &local);
socketDescriptor = socket(local->ai_family, local->ai_socktype, local->ai_protocol);
int on = 1;
setsockopt(socketDescriptor, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&on), sizeof(int));
if (local->ai_family == AF_INET6) {
int off = 0;
setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char *>(&off), sizeof(int));
}
if (bind(socketDescriptor, local->ai_addr, local->ai_addrlen)) {
// Save our actual local address for later use.
getsockname(socketDescriptor, reinterpret_cast<sockaddr *>(&localAddr.addr), &localAddr.addrlen);
}
}
void ServerListener::onReadable(struct ev_loop *, ev_io * watcher, int) {
auto self = static_cast<ServerListener *>(watcher->data);
uint8_t buf[2048];
ngtcp2_addr remote;
auto len = recvfrom(self->socketDescriptor, buf, sizeof(buf), 0, remote.addr, &remote.addrlen);
ngtcp2_pkt_info pi{};
ngtcp2_pkt_hd hd{};
ngtcp2_pkt_decode_hd_long(&hd, buf, len);
auto it = self->connections.find(hd.dcid);
if (it != self->connections.end()) {
ServerConnection * conn = it->second.get();
ngtcp2_conn_read_pkt(conn->conn, &conn->path, &pi, buf, len, std::chrono::steady_clock::now().time_since_epoch().count());
conn->readPacket(&pi, buf);
return;
}
if (hd.flags & NGTCP2_PKT_FLAG_LONG_FORM && hd.type == NGTCP2_PKT_INITIAL) {
ngtcp2_pkt_hd accept_hd{};
if (ngtcp2_accept(&accept_hd, buf, static_cast<size_t>(len)) != 0) {
return;
}
auto new_conn = std::make_unique<ServerConnection>(ngtcp2_path {
.local = self->localAddr,
.remote = remote
});
self->connections[accept_hd.dcid] = std::move(new_conn);
}
}
void ServerListener::run() {
std::thread([this]() {
loop = ev_loop_new(EVFLAG_AUTO);
ev_io_init(&watcher, onReadable, socketDescriptor, EV_READ);
watcher.data = this;
ev_io_start(loop, &watcher);
ev_run(loop, 0);
}).detach();
}
}

View file

@ -0,0 +1,95 @@
#pragma once
#include <string>
#include <unordered_map>
#include <ngtcp2/ngtcp2.h>
#include <gnutls/gnutls.h>
#include <ev.h>
// Platform-specific socket includes and helpers
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
using socket_t = SOCKET;
#define CLOSE_SOCKET closesocket
#define SOCKLEN_T int
#define IS_INVALID_SOCKET(s) ((s) == INVALID_SOCKET)
#define GET_LAST_ERROR() WSAGetLastError()
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <netdb.h>
using socket_t = int;
#define CLOSE_SOCKET close
#define SOCKLEN_T socklen_t
#define IS_INVALID_SOCKET(s) ((s) < 0)
#define GET_LAST_ERROR() errno
#endif
namespace Artifact {
namespace {
struct Ngtcp2CidEqual {
bool operator()(const ngtcp2_cid& a, const ngtcp2_cid& b) const noexcept {
return a.datalen == b.datalen &&
std::memcmp(a.data, b.data, a.datalen) == 0;
}
};
struct Ngtcp2CidHash {
size_t operator()(const ngtcp2_cid& cid) const noexcept {
size_t h = cid.datalen;
for (uint8_t i = 0; i < cid.datalen; ++i) {
h = (h * 31) ^ cid.data[i]; // or better hash as above
}
return h;
}
};
}
class Connection {
public:
ngtcp2_settings settings;
ngtcp2_transport_params transportParams;
ngtcp2_callbacks callbacks;
ngtcp2_cid dcid, scid;
gnutls_session_t session;
ngtcp2_conn * conn;
Connection();
void readPacket(ngtcp2_pkt_info * info, void * packet);
};
class ClientConnection: public Connection {
public:
ClientConnection(std::string host, std::string port);
};
class ServerConnection: public Connection {
public:
ngtcp2_path path;
ServerConnection(ngtcp2_path path);
};
class ServerListener {
socket_t socketDescriptor;
std::unordered_map<ngtcp2_cid, std::unique_ptr<ServerConnection>, Ngtcp2CidHash, Ngtcp2CidEqual> connections;
ngtcp2_addr localAddr;
struct ev_loop * loop;
ev_io watcher;
public:
ServerListener(std::string port);
static void onReadable(struct ev_loop *, ev_io * watcher, int);
void run();
};
}

View file

@ -0,0 +1,31 @@
#include "Network.h"
namespace Artifact {
void NetworkServer::host(NetworkClient * local) {
if (active) {
unhost();
}
active = true;
localClient = local;
listener = local->listen<Events::NetworkMessage>([](auto ev) {
});
}
void NetworkServer::unhost() {
active = false;
if (localClient) {
localClient->unlisten<Events::NetworkMessage>(listener);
localClient = nullptr;
} else if (server) {
server = nullptr;
}
}
void NetworkClient::connect(NetworkServer * local) {
localServer = local;
}
}

45
Shared/Network/Network.h Normal file
View file

@ -0,0 +1,45 @@
#pragma once
#include "Shared.h"
#include "Events.h"
#include "Connection.h"
namespace Artifact {
namespace Events {
struct NetworkMessage {
void * data;
};
}
class NetworkClient;
class NetworkServer: public BaseSubsystem, public EventTarget {
NetworkClient * localClient = nullptr;
std::unique_ptr<ServerListener> server = nullptr;
bool active = false;
uint64_t listener;
public:
void host(NetworkClient * client);
void host(std::string port);
void unhost();
};
class NetworkClient: public BaseSubsystem, public EventTarget {
NetworkServer * localServer = nullptr;
std::unique_ptr<Connection> server = nullptr;
bool active = false;
uint64_t listener;
public:
void connect(NetworkServer * local);
void connect(std::string host, std::string port);
void disconnect();
};
}

7
Shared/Shared.cpp Normal file
View file

@ -0,0 +1,7 @@
#include "Shared.h"
namespace Artifact {
}

86
Shared/Shared.h Normal file
View file

@ -0,0 +1,86 @@
#pragma once
#include <vector>
#include <glm/glm.hpp>
namespace std {
template<>
struct hash<glm::ivec3> {
std::size_t operator()(const glm::ivec3& v) const noexcept {
// Very common and reasonably good quality combination for floats
// (tries to mix bits while being fast)
std::size_t seed = 0;
// You can use any of these styles — pick one:
// ────────────────────────────────────────────────────────────────
// Style 1: Boost hash_combine pattern (very popular)
auto hash_float = [](float f) -> std::size_t {
return std::hash<float>{}(f);
};
seed ^= hash_float(v.x) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
seed ^= hash_float(v.y) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
seed ^= hash_float(v.z) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
// ────────────────────────────────────────────────────────────────
// Style 2: Simpler — fold with multiplication (often enough)
// seed = std::hash<float>{}(v.x);
// seed = seed * 31 + std::hash<float>{}(v.y);
// seed = seed * 31 + std::hash<float>{}(v.z);
// ────────────────────────────────────────────────────────────────
// Style 3: Treat as 12-byte blob → good quality, but slower on some platforms
// std::size_t h = 0;
// std::memcpy(&h, &v, sizeof(float)); // x
// h ^= std::hash<std::uint32_t>{}(h);
// std::memcpy(&h, reinterpret_cast<const char*>(&v) + 4, sizeof(float));
// h ^= std::hash<std::uint32_t>{}(h) * 0x85ebca6b;
// std::memcpy(&h, reinterpret_cast<const char*>(&v) + 8, sizeof(float));
// return std::hash<std::uint32_t>{}(h);
return seed;
}
};
} // namespace std
namespace Artifact {
template<typename Subsystem>
class Engine {
public:
std::vector<std::unique_ptr<Subsystem>> subsystems;
template <typename T, typename... Args>
T * addSubsystem(Args &&... args) {
auto system = std::make_unique<T>(std::forward<Args>(args)...);
auto ptr = system.get();
subsystems.push_back(std::move(system));
return ptr;
}
template <typename T>
T * subsystem() const {
for (const auto & system : subsystems) {
if (T * ptr = dynamic_cast<T *>(system.get())) {
return ptr;
}
}
return nullptr;
}
};
class BaseSubsystem {
public:
Engine<BaseSubsystem> * engine = nullptr;
virtual void init() {}
virtual void reload() {}
virtual void deinit() {}
virtual void render() {}
virtual void tick() {}
};
}

9
Shared/World/Chunk.h Normal file
View file

@ -0,0 +1,9 @@
#include <vector>
#include <glm/glm/glm.hpp>
struct Chunk {
glm::ivec3 pos;
std::vector<uint16_t> data;
bool dirty = false;
};