Initial commit.
This commit is contained in:
commit
82b4f23c06
56 changed files with 3485 additions and 0 deletions
6
Shared/CMakeLists.txt
Normal file
6
Shared/CMakeLists.txt
Normal 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
91
Shared/Events.h
Normal 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();
|
||||
// }
|
||||
}
|
||||
};
|
||||
148
Shared/Network/Connection.cpp
Normal file
148
Shared/Network/Connection.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
95
Shared/Network/Connection.h
Normal file
95
Shared/Network/Connection.h
Normal 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();
|
||||
};
|
||||
|
||||
}
|
||||
31
Shared/Network/Network.cpp
Normal file
31
Shared/Network/Network.cpp
Normal 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
45
Shared/Network/Network.h
Normal 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();
|
||||
};
|
||||
|
||||
}
|
||||
60
Shared/Paths.cpp
Normal file
60
Shared/Paths.cpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#include "Paths.h"
|
||||
|
||||
namespace Artifact {
|
||||
|
||||
Path getDataPath() {
|
||||
Path base;
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// 1. Windows — AppData/Roaming (most common choice)
|
||||
// ────────────────────────────────────────────────
|
||||
#ifdef _WIN32
|
||||
const char* appdata = std::getenv("APPDATA");
|
||||
if (!appdata || !fs::exists(appdata)) {
|
||||
throw std::runtime_error("Cannot find APPDATA environment variable");
|
||||
}
|
||||
base = Path(appdata) / organization / GAME;
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
// ────────────────────────────────────────────────
|
||||
// 2. macOS — Application Support
|
||||
// ────────────────────────────────────────────────
|
||||
const char* home = std::getenv("HOME");
|
||||
if (!home) throw std::runtime_error("Cannot find HOME");
|
||||
base = Path(home) / "Library" / "Application Support" / GAME;
|
||||
|
||||
#else
|
||||
// ────────────────────────────────────────────────
|
||||
// 3. Linux / BSD / Steam Deck — XDG_DATA_HOME
|
||||
// ────────────────────────────────────────────────
|
||||
const char* xdg_data = std::getenv("XDG_DATA_HOME");
|
||||
if (xdg_data && *xdg_data) {
|
||||
base = fs::path(xdg_data) / game;
|
||||
} else {
|
||||
const char* home = std::getenv("HOME");
|
||||
if (!home) throw std::runtime_error("Cannot find HOME");
|
||||
base = Path(home) / ".local" / "share" / GAME;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Create folder structure if it doesn't exist
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(base, ec);
|
||||
|
||||
if (ec) {
|
||||
throw std::runtime_error("Failed to create directory: " +
|
||||
base.string() + " → " + ec.message());
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
Path getClientConfigPath() {
|
||||
return getDataPath() / "client.conf";
|
||||
}
|
||||
|
||||
Path getServerConfigPath() {
|
||||
return getDataPath() / "server.conf";
|
||||
}
|
||||
|
||||
}
|
||||
24
Shared/Paths.h
Normal file
24
Shared/Paths.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
|
||||
#ifndef APP_NAME
|
||||
# define APP_NAME ArtifactEngine
|
||||
#endif
|
||||
|
||||
#define TOSTRING(x) #x
|
||||
|
||||
#define GAME TOSTRING(APP_NAME)
|
||||
|
||||
namespace Artifact {
|
||||
|
||||
using Path = std::filesystem::path;
|
||||
|
||||
Path getDataPath();
|
||||
|
||||
Path getClientConfigPath();
|
||||
|
||||
Path getServerConfigPath();
|
||||
|
||||
}
|
||||
58
Shared/Settings.cpp
Normal file
58
Shared/Settings.cpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#include "Settings.h"
|
||||
|
||||
namespace Artifact {
|
||||
|
||||
Settings::Settings() : Settings(getDataPath() / "settings.conf") {}
|
||||
|
||||
Settings::Settings(Path path) : path(path) {
|
||||
reload();
|
||||
}
|
||||
|
||||
std::string trim(const std::string& str) {
|
||||
size_t first = str.find_first_not_of(" \t\r\n");
|
||||
if (first == std::string::npos) return "";
|
||||
size_t last = str.find_last_not_of(" \t\r\n");
|
||||
return str.substr(first, last - first + 1);
|
||||
}
|
||||
|
||||
void Settings::reload() {
|
||||
clear();
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open()) {
|
||||
// TODO: Log a warning
|
||||
return;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
line = trim(line);
|
||||
if (line.empty() || line[0] == '#' || line[0] == ';') {
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t eq_pos = line.find('=');
|
||||
if (eq_pos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string key = trim(line.substr(0, eq_pos));
|
||||
std::string value = trim(line.substr(eq_pos + 1));
|
||||
|
||||
if (!key.empty()) {
|
||||
data[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::save() {
|
||||
std::ofstream file(path);
|
||||
if (!file.is_open()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto & [key, value] : data) {
|
||||
file << key << " = " << value << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
85
Shared/Settings.h
Normal file
85
Shared/Settings.h
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <optional>
|
||||
|
||||
#include "Paths.h"
|
||||
#include "Events.h"
|
||||
|
||||
namespace Artifact {
|
||||
|
||||
namespace Events {
|
||||
|
||||
struct SettingChanged {
|
||||
std::string key;
|
||||
std::optional<std::string> value;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
class Settings: public EventTarget {
|
||||
const Path path;
|
||||
std::unordered_map<std::string, std::string> data;
|
||||
public:
|
||||
Settings();
|
||||
Settings(Path path);
|
||||
|
||||
void reload();
|
||||
void save();
|
||||
|
||||
template<typename T>
|
||||
T get(std::string & key, const T & fallback = {}) {
|
||||
auto it = data.find(key);
|
||||
if (it == data.end()) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const std::string & str = it->second;
|
||||
|
||||
if constexpr (std::is_same_v<T, std::string>) {
|
||||
return str;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, bool>) {
|
||||
std::string lower = str;
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(),
|
||||
[](unsigned char c){ return static_cast<char>(std::tolower(c)); });
|
||||
if (lower == "true" || lower == "1" || lower == "yes" || lower == "on") {
|
||||
return true;
|
||||
}
|
||||
if (lower == "false" || lower == "0" || lower == "no" || lower == "off") {
|
||||
return false;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
std::istringstream iss(str);
|
||||
T value;
|
||||
if (iss >> value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void set(const std::string & key, const T & value) {
|
||||
std::ostringstream oss;
|
||||
oss << std::boolalpha << value;
|
||||
data[key] = oss.str();
|
||||
dispatch(Events::SettingChanged { .key = key, .value = value });
|
||||
}
|
||||
|
||||
void reset(const std::string & key) {
|
||||
data.erase(key);
|
||||
dispatch(Events::SettingChanged { .key = key, .value = std::nullopt });
|
||||
}
|
||||
|
||||
void clear() {
|
||||
data.clear();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
11
Shared/Shared.cpp
Normal file
11
Shared/Shared.cpp
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#include <iostream>
|
||||
|
||||
#include "Shared.h"
|
||||
|
||||
namespace Artifact {
|
||||
|
||||
void log(std::string msg) {
|
||||
std::cout << msg << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
147
Shared/Shared.h
Normal file
147
Shared/Shared.h
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
#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:
|
||||
virtual void init() {}
|
||||
virtual void reload() {}
|
||||
virtual void deinit() {}
|
||||
};
|
||||
|
||||
template<typename T> requires std::integral<T> || std::floating_point<T>
|
||||
class Composed {
|
||||
enum ModifierType {
|
||||
Add, Multiply
|
||||
};
|
||||
using Modifier = std::pair<ModifierType, T>;
|
||||
|
||||
uint64_t nextID = 0;
|
||||
|
||||
std::vector<Modifier> modifiers;
|
||||
T _value;
|
||||
public:
|
||||
Composed() : Composed(T{0}) {}
|
||||
Composed(T base) {
|
||||
add(Add, base);
|
||||
}
|
||||
|
||||
T value() {
|
||||
return _value;
|
||||
}
|
||||
|
||||
void recompute() {
|
||||
T sum;
|
||||
T fac;
|
||||
for (auto [type, value] : modifiers) {
|
||||
if (type == Add) {
|
||||
sum += value;
|
||||
} else if (type == Multiply) {
|
||||
fac *= value;
|
||||
}
|
||||
}
|
||||
|
||||
_value = sum * fac;
|
||||
}
|
||||
|
||||
uint64_t add(ModifierType type, T value) {
|
||||
modifiers.emplace_back(type, value);
|
||||
recompute();
|
||||
return nextID++;
|
||||
}
|
||||
|
||||
bool remove(uint64_t id) {
|
||||
auto it = std::find_if(modifiers.begin(), modifiers.end(),
|
||||
[id](const Modifier & m) { return m.id == id; });
|
||||
if (it == modifiers.end()) return false;
|
||||
|
||||
modifiers.erase(it);
|
||||
recompute();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool update(uint64_t id, T newValue) {
|
||||
auto it = std::find_if(modifiers.begin(), modifiers.end(),
|
||||
[id](const Modifier& m) { return m.id == id; });
|
||||
if (it == modifiers.end()) return false;
|
||||
|
||||
it->value = newValue;
|
||||
recompute();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void log(std::string msg);
|
||||
|
||||
}
|
||||
9
Shared/World/Chunk.h
Normal file
9
Shared/World/Chunk.h
Normal 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;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue