Skip to content

Commit

Permalink
stash: non-WASM build with LWS
Browse files Browse the repository at this point in the history
  • Loading branch information
Desdaemon committed Dec 3, 2024
1 parent 119336f commit c53073f
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 17 deletions.
27 changes: 26 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16...3.28 FATAL_ERROR)
project(EasyRPG_Player VERSION 0.8
DESCRIPTION "Interpreter for RPG Maker 2000/2003 games"
HOMEPAGE_URL "https://easyrpg.org"
LANGUAGES CXX)
LANGUAGES C CXX)

# Extra CMake Module files
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/builds/cmake/Modules")
Expand All @@ -13,6 +13,7 @@ include(PlayerFindPackage)
include(PlayerBuildType)
include(PlayerMisc)
include(GetGitRevisionDescription)
include(CheckCSourceCompiles)

# Dependencies provided by CMake Presets
list(APPEND CMAKE_PREFIX_PATH "${PLAYER_PREFIX_PATH_APPEND}")
Expand Down Expand Up @@ -922,6 +923,30 @@ else()
TARGET nlohmann_json::nlohmann_json
ONLY_CONFIG
)

find_package(Libwebsockets CONFIG REQUIRED)
require_lws_config(LWS_ROLE_H1 1 requirements)
require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)
# require_lws_config(LWS_WITH_SECURE_STREAMS_CPP 1 requirements)
require_lws_config(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY 0 requirements)
require_lws_config(LWS_WITH_TLS 1 requirements)
require_lws_config(LWS_WITH_SECURE_STREAMS_AUTH_SIGV4 0 requirements)

# uses system trust store
require_lws_config(LWS_WITH_MBEDTLS 0 requirements)
require_lws_config(LWS_WITH_WOLFSSL 0 requirements)
require_lws_config(LWS_WITH_CYASSL 0 requirements)

# require_lws_config(LWS_WITH_SYS_FAULT_INJECTION 1 has_fault_injection)
# require_lws_config(LWS_WITH_SECURE_STREAMS_PROXY_API 1 has_ss_proxy)
# require_lws_config(LWS_WITH_SYS_STATE 1 has_sys_state)
if(websockets_shared)
target_link_libraries(${PROJECT_NAME} websockets_shared ${LIBWEBSOCKETS_DEP_LIBS})
add_dependencies(${PROJECT_NAME} websockets_shared)
else()
target_link_libraries(${PROJECT_NAME} websockets ${LIBWEBSOCKETS_DEP_LIBS})
endif()
endif()

# Sound system to use
Expand Down
3 changes: 2 additions & 1 deletion builds/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ android {
cmake {
arguments "-DPLAYER_GRADLE_BUILD=ON",
"-DBUILD_SHARED_LIBS=ON",
"-DPLAYER_ENABLE_TESTS=OFF"
"-DPLAYER_ENABLE_TESTS=OFF",
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"

if (project.hasProperty("toolchainDirs")) {
arguments.add('-DPLAYER_ANDROID_TOOLCHAIN_PATH=' + project.properties['toolchainDirs'])
Expand Down
8 changes: 3 additions & 5 deletions src/game_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
#include <lcf/rpg/save.h>
#include "scene_gameover.h"
#include "multiplayer/game_multiplayer.h"
#include <emscripten/emscripten.h>
#include "web_api.h"
#include "feature.h"

namespace {
Expand Down Expand Up @@ -344,9 +344,7 @@ std::unique_ptr<lcf::rpg::Map> Game_Map::LoadMapFile(int map_id) {

Output::Debug("Loaded Map {}", map_name);

EM_ASM({
onLoadMap(UTF8ToString($0));
}, map_name.c_str());
Web_API::OnLoadMap(map_name);

if (map.get() == NULL) {
Output::ErrorStr(lcf::LcfReader::GetError());
Expand Down Expand Up @@ -1252,7 +1250,7 @@ void Game_Map::Update(MapUpdateAsyncContext& actx, bool is_preupdate) {
//If not resuming from async op ...
Main_Data::game_player->Update();
GMI().Update();

for (auto& vehicle: vehicles) {
if (vehicle.GetMapId() == GetMapId()) {
vehicle.Update();
Expand Down
183 changes: 175 additions & 8 deletions src/multiplayer/yno_connection.cpp
Original file line number Diff line number Diff line change
@@ -1,36 +1,49 @@
#include "yno_connection.h"
#include <emscripten/websocket.h>
#include "output.h"
#ifdef __EMSCRIPTEN__
# include <emscripten/websocket.h>
#else
# include <libwebsockets.h>
# include <thread>
#endif
#include "../external/TinySHA1.hpp"

struct YNOConnection::IMPL {
#ifdef __EMSCRIPTEN__
EMSCRIPTEN_WEBSOCKET_T socket;
#else
lws_ss_handle* handle;
lws_context* cx;
#endif

uint32_t msg_count;
bool closed;

static EM_BOOL onopen(int eventType, const EmscriptenWebSocketOpenEvent *event, void *userData) {
#ifdef __EMSCRIPTEN__
static bool onopen(int eventType, const EmscriptenWebSocketOpenEvent *event, void *userData) {
auto _this = static_cast<YNOConnection*>(userData);
_this->SetConnected(true);
_this->DispatchSystem(SystemMessage::OPEN);
return EM_TRUE;
return true;
}
static EM_BOOL onclose(int eventType, const EmscriptenWebSocketCloseEvent *event, void *userData) {
static bool onclose(int eventType, const EmscriptenWebSocketCloseEvent *event, void *userData) {
auto _this = static_cast<YNOConnection*>(userData);
_this->SetConnected(false);
_this->DispatchSystem(
event->code == 1028 ?
SystemMessage::EXIT :
SystemMessage::CLOSE
);
return EM_TRUE;
return true;
}
static EM_BOOL onmessage(int eventType, const EmscriptenWebSocketMessageEvent *event, void *userData) {
static bool onmessage(int eventType, const EmscriptenWebSocketMessageEvent *event, void *userData) {
auto _this = static_cast<YNOConnection*>(userData);
// IMPORTANT!! numBytes is always one byte larger than the actual length
// so the actual length is numBytes - 1

// NOTE: that extra byte is just in text mode, and it does not exist in binary mode
if (event->isText) {
return EM_FALSE;
return false;
}
std::string_view cstr(reinterpret_cast<const char*>(event->data), event->numBytes);
std::vector<std::string_view> mstrs = Split(cstr, Multiplayer::Packet::MSG_DELIM);
Expand All @@ -50,14 +63,146 @@ struct YNOConnection::IMPL {
_this->Dispatch(namestr, Split(argstr));
}
}
return EM_TRUE;
return true;
}
#else
typedef struct {
struct lws_ss_handle* ss;
void* opaque_data;

// custom logic fields begin
YNOConnection* conn;
// custom logic fields end

lws_sorted_usec_list_t sul;
int count;
bool due;
} yno_socket_t;

static lws_ss_state_return_t yno_socket_rx(void* userData, const uint8_t* in, size_t len, int flags)
{
auto* self = static_cast<yno_socket_t*>(userData);
auto* h = self->ss;

std::string_view cstr(reinterpret_cast<const char*>(in), len);
std::vector<std::string_view> mstrs = Split(cstr, Multiplayer::Packet::MSG_DELIM);
for (auto& mstr : mstrs) {
auto p = mstr.find(Multiplayer::Packet::PARAM_DELIM);
if (p == mstr.npos) {
/*
Usually npos is the maximum value of size_t.
Adding to it is undefined behavior.
If it returns end iterator instead of npos, the if statement is
duplicated code because the statement in else clause will handle it.
*/
self->conn->Dispatch(mstr);
} else {
auto namestr = mstr.substr(0, p);
auto argstr = mstr.substr(p + Multiplayer::Packet::PARAM_DELIM.size());
self->conn->Dispatch(namestr, Split(argstr));
}
}
return LWSSSSRET_OK;
}

static constexpr uint RATE_US = 50000;
static constexpr uint PKT_SIZE = 80;

static void yno_socket_txcb(struct lws_sorted_usec_list* sul)
{
auto* self = lws_container_of(sul, yno_socket_t, sul);

self->due = true;
if (lws_ss_request_tx(self->ss) != LWSSSSRET_OK) {
// TODO: The fuck you expect me to do?
}

lws_sul_schedule(lws_ss_get_context(self->ss), 0, &self->sul, yno_socket_txcb, RATE_US);
}

static lws_ss_state_return_t yno_socket_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, int *flags)
{
auto* self = static_cast<yno_socket_t *>(userobj);
if (!self->due)
return LWSSSSRET_TX_DONT_SEND;

self->due = false;

if (lws_get_random(lws_ss_get_context(self->ss), buf, PKT_SIZE) != PKT_SIZE)
return LWSSSSRET_TX_DONT_SEND;

*len = PKT_SIZE;
*flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM;

self->count++;

lws_sul_schedule(lws_ss_get_context(self->ss), 0, &self->sul, yno_socket_txcb, RATE_US);

return LWSSSSRET_OK;
}


static lws_ss_state_return_t yno_socket_state(void* userData, void* h_src, lws_ss_constate_t state, lws_ss_tx_ordinal_t sck)
{
auto* self = static_cast<yno_socket_t*>(userData);
switch (state) {
case LWSSSCS_CREATING:
return lws_ss_request_tx(self->ss);
case LWSSSCS_CONNECTED:
self->conn->SetConnected(true);
self->conn->DispatchSystem(SystemMessage::OPEN);
lws_sul_schedule(lws_ss_get_context(self->ss), 0, &self->sul, yno_socket_txcb, RATE_US);
break;
case LWSSSCS_DISCONNECTED:
self->conn->SetConnected(false);
self->conn->DispatchSystem(SystemMessage::CLOSE);
lws_sul_cancel(&self->sul);
break;
default:
break;
}
return LWSSSSRET_OK;
}

static constexpr lws_ss_info_t ssi {
.streamtype = "yno",
.user_alloc = sizeof(yno_socket_t),
.handle_offset = offsetof(yno_socket_t, ss),
.opaque_user_data_offset = offsetof(yno_socket_t, opaque_data),
.rx = yno_socket_rx,
.tx = yno_socket_tx,
.state = yno_socket_state,
};
#endif

#ifdef __EMSCRIPTEN__
static void set_callbacks(int socket, void* userData) {
emscripten_websocket_set_onopen_callback(socket, userData, onopen);
emscripten_websocket_set_onclose_callback(socket, userData, onclose);
emscripten_websocket_set_onmessage_callback(socket, userData, onmessage);
}
#endif
void initWs(lws_ss_policy* policy) {
#ifndef __EMSCRIPTEN__
lws_set_log_level(LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE, nullptr);
lws_context_creation_info info {
.protocols = lws_sspc_protocols,
.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT,
.fd_limit_per_thread = 1 + 6 + 1,
.port = CONTEXT_PORT_NO_LISTEN,
.pss_policies = policy,
};
cx = lws_create_context(&info);
if (!cx) Output::ErrorStr("lws_create_context failed");

if (lws_ss_create(cx, 0, &ssi, nullptr, &handle, nullptr, nullptr))
Output::ErrorStr("lws_ss_create failed");

std::thread event_thread([](lws_context* cx) {
while (!lws_service(cx, 0));
}, cx);
}
#endif
};

const size_t YNOConnection::MAX_QUEUE_SIZE{ 4088 };
Expand All @@ -70,14 +215,18 @@ YNOConnection::YNOConnection() : impl(new IMPL) {

YNOConnection::YNOConnection(YNOConnection&& o)
: Connection(std::move(o)), impl(std::move(o.impl)) {
#ifdef __EMSCRIPTEN__
IMPL::set_callbacks(impl->socket, this);
#endif
}
YNOConnection& YNOConnection::operator=(YNOConnection&& o) {
Connection::operator=(std::move(o));
if (this != &o) {
Close();
impl = std::move(o.impl);
#ifdef __EMSCRIPTEN__
IMPL::set_callbacks(impl->socket, this);
#endif
}
return *this;
}
Expand All @@ -92,6 +241,7 @@ void YNOConnection::Open(std::string_view uri) {
Close();
}

#ifdef __EMSCRIPTEN__
std::string s {uri};
EmscriptenWebSocketCreateAttributes ws_attrs = {
s.data(),
Expand All @@ -101,18 +251,31 @@ void YNOConnection::Open(std::string_view uri) {
impl->socket = emscripten_websocket_new(&ws_attrs);
impl->closed = false;
IMPL::set_callbacks(impl->socket, this);
#else
const lws_ss_policy_t yno_policy {
.streamtype = "yno",
.endpoint = uri.data(),
.port = 443,
.protocol = (uint8_t)(uri.find("wss") == 0 ? 1 : 0),
};
#endif
impl->initWs();
}

void YNOConnection::Close() {
Multiplayer::Connection::Close();
if (impl->closed)
return;
impl->closed = true;
#ifdef __EMSCRIPTEN__
// strange bug:
// calling with (impl->socket, 1005, "any reason") raises exceptions
// might be an emscripten bug
emscripten_websocket_close(impl->socket, 0, nullptr);
emscripten_websocket_delete(impl->socket);
#else
if (impl->handle) lws_ss_destroy(&impl->handle);
#endif
}

template<typename T>
Expand Down Expand Up @@ -182,7 +345,11 @@ void YNOConnection::Send(std::string_view data) {
if (!IsConnected())
return;
unsigned short ready;
#ifdef __EMSCRIPTEN__
emscripten_websocket_get_ready_state(impl->socket, &ready);
#else
ready = impl->ready;
#endif
if (ready == 1) { // OPEN
++impl->msg_count;
auto sendmsg = calculate_header(GetKey(), impl->msg_count, data);
Expand Down
8 changes: 6 additions & 2 deletions src/web_api.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#include "web_api.h"
#include "emscripten/emscripten.h"
#include "output.h"
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#else
#define EM_ASM_INT(x, ...) 0
#define EM_ASM(x, ...)
#endif

using namespace Web_API;

Expand Down

0 comments on commit c53073f

Please sign in to comment.