mirror of
https://github.com/ondra-novak/gates_of_skeldal.git
synced 2025-07-20 05:04:53 -04:00
sse client to listen commands (from mapedit)
This commit is contained in:
parent
13f6c05c60
commit
bb5be10adc
10 changed files with 336 additions and 29 deletions
|
@ -39,7 +39,7 @@
|
|||
#define E_MENU_SELECT 36
|
||||
#define E_CLOSE_MAP 37
|
||||
#define E_CLOSE_GEN 38
|
||||
#define E_HACKER 39
|
||||
#define E_EXTERNAL_MSG 40 //external message arrived from sse_listener
|
||||
|
||||
//side flags
|
||||
|
||||
|
|
|
@ -280,6 +280,10 @@ INIS sinit[]=
|
|||
int last_ms_cursor=-1;
|
||||
int vmode=2;
|
||||
|
||||
#include <platform/sse_receiver.h>
|
||||
|
||||
static SSE_RECEIVER *sse_receiver = NULL;
|
||||
static MTQUEUE *mtqueue = NULL;
|
||||
|
||||
void purge_temps(char _) {
|
||||
temp_storage_clear();
|
||||
|
@ -741,7 +745,8 @@ void done_skeldal(void)
|
|||
cur_config = NULL;
|
||||
}
|
||||
kill_timer();
|
||||
|
||||
if (sse_receiver) sse_receiver_stop(sse_receiver);
|
||||
if (mtqueue) mtqueue_destroy(mtqueue);
|
||||
}
|
||||
|
||||
|
||||
|
@ -980,6 +985,38 @@ void show_loading_picture(char *filename)
|
|||
ablock_free(p);
|
||||
}
|
||||
|
||||
void sse_listener_watch(EVENT_MSG *msg, void **userdata) {
|
||||
if (msg->msg == E_WATCH) {
|
||||
char *s = mtqueue_pop(mtqueue);
|
||||
if (s) {
|
||||
send_message(E_EXTERNAL_MSG, s);
|
||||
free(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
void sse_listener_init(const char *hostport) {
|
||||
|
||||
char *host = local_strdup(hostport);
|
||||
char *port = strrchr(host,':');
|
||||
if (port == NULL) {
|
||||
port = local_strdup("80");
|
||||
} else {
|
||||
*port = 0;
|
||||
++port;
|
||||
}
|
||||
|
||||
MTQUEUE *q = mtqueue_create();
|
||||
SSE_RECEIVER *rcv = sse_receiver_install(q, host, port);
|
||||
|
||||
if (rcv == NULL) {
|
||||
mtqueue_destroy(q);
|
||||
return;
|
||||
}
|
||||
mtqueue = q;
|
||||
sse_receiver = rcv;
|
||||
send_message(E_ADD, E_WATCH, sse_listener_watch);
|
||||
|
||||
}
|
||||
|
||||
void init_skeldal(const INI_CONFIG *cfg)
|
||||
{
|
||||
|
@ -989,10 +1026,11 @@ void init_skeldal(const INI_CONFIG *cfg)
|
|||
|
||||
cti_texty();
|
||||
timer_tree.next=NULL;
|
||||
init_events();
|
||||
init_events();
|
||||
|
||||
steam_init();
|
||||
|
||||
|
||||
char verr = game_display_init(ini_section_open(cfg, "video"), "Skeldal");
|
||||
if (!verr)
|
||||
{
|
||||
|
@ -1120,15 +1158,21 @@ char doNotLoadMapState=0;
|
|||
static int reload_map_handler(EVENT_MSG *msg,void **usr)
|
||||
{
|
||||
extern char running_battle;
|
||||
if (msg->msg==E_RELOADMAP)
|
||||
if (msg->msg==E_EXTERNAL_MSG)
|
||||
{
|
||||
int i;
|
||||
ReloadMapInfo *minfo=va_arg(msg->data, ReloadMapInfo *);
|
||||
const char *fname=minfo->fname;
|
||||
int sektor=minfo->sektor;
|
||||
strcopy_n(loadlevel.name,fname,sizeof(loadlevel.name));
|
||||
loadlevel.start_pos=sektor;
|
||||
for(i=0;i<POCET_POSTAV;i++)postavy[i].sektor=loadlevel.start_pos;
|
||||
const char *m = va_arg(msg->data, const char *);
|
||||
char fname[13];
|
||||
int sector;
|
||||
int i;
|
||||
|
||||
if (sscanf(m, "RELOAD %12s %d", fname, §or) != 2) return 0;
|
||||
|
||||
strcopy_n(loadlevel.name,fname,sizeof(loadlevel.name));
|
||||
loadlevel.start_pos=sector;
|
||||
for(i=0;i<POCET_POSTAV;i++) {
|
||||
postavy[i].sektor=loadlevel.start_pos;
|
||||
postavy[i].groupnum = 1;
|
||||
}
|
||||
SEND_LOG("(WIZARD) Load map '%s' %d",loadlevel.name,loadlevel.start_pos);
|
||||
unwire_proc();
|
||||
if (battle) konec_kola();
|
||||
|
@ -1751,6 +1795,10 @@ int skeldal_entry_point(const SKELDAL_CONFIG *start_cfg)
|
|||
enable_achievements(1);
|
||||
}
|
||||
|
||||
if (start_cfg->sse_hostport) {
|
||||
sse_listener_init(start_cfg->sse_hostport);
|
||||
}
|
||||
|
||||
start_check();
|
||||
purge_temps(1);
|
||||
clrscr();
|
||||
|
|
|
@ -13,6 +13,7 @@ typedef struct {
|
|||
const char *config_path;
|
||||
const char *lang_path;
|
||||
|
||||
const char *sse_hostport;
|
||||
|
||||
} SKELDAL_CONFIG;
|
||||
|
||||
|
|
|
@ -12,17 +12,19 @@ target_sources(skeldal_platform PRIVATE
|
|||
timer.cpp
|
||||
getopt.c
|
||||
achievements.cpp
|
||||
mtqueue.cpp
|
||||
sse_receiver.cpp
|
||||
)
|
||||
|
||||
set(all_libs
|
||||
skeldal_main
|
||||
skeldal_libs
|
||||
set(all_libs
|
||||
skeldal_main
|
||||
skeldal_libs
|
||||
skeldal_platform
|
||||
skeldal_sdl
|
||||
skeldal_libs
|
||||
${SDL2_LIBRARIES}
|
||||
${STANDARD_LIBRARIES})
|
||||
|
||||
|
||||
if(WIN32)
|
||||
target_sources(skeldal_platform PRIVATE
|
||||
windows/save_folder.cpp
|
||||
|
@ -35,7 +37,7 @@ if(WIN32)
|
|||
windows/skeldal.rc
|
||||
)
|
||||
target_compile_definitions(skeldal_platform PRIVATE PLATFORM_WINDOWS)
|
||||
if(STEAM_ENABLED)
|
||||
if(STEAM_ENABLED)
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
# 64-bit
|
||||
set(STEAMLIB ${STEAMWORKS_SDK_DIR}/redistributable_bin/win64/steam_api64.lib)
|
||||
|
@ -49,7 +51,7 @@ if(WIN32)
|
|||
set(STEAMLIB "")
|
||||
set(STEAMDLL "")
|
||||
endif()
|
||||
|
||||
|
||||
target_link_libraries(skeldal ${all_libs} ${STEAMLIB})
|
||||
|
||||
if(STEAMDLL)
|
||||
|
@ -69,7 +71,7 @@ elseif(UNIX AND NOT APPLE)
|
|||
linux/app_start.cpp
|
||||
)
|
||||
target_compile_definitions(skeldal_bin PRIVATE PLATFORM_LINUX)
|
||||
if(STEAM_ENABLED)
|
||||
if(STEAM_ENABLED)
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
# 64-bit
|
||||
set(STEAMLIB ${STEAMWORKS_SDK_DIR}/redistributable_bin/linux64/libsteam_api.so)
|
||||
|
@ -83,10 +85,10 @@ elseif(UNIX AND NOT APPLE)
|
|||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_CURRENT_LIST_DIR}/linux/skeldal.sh
|
||||
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/skeldal.sh)
|
||||
target_link_libraries(skeldal_bin ${all_libs} ${STEAMLIB})
|
||||
target_link_libraries(skeldal_bin ${all_libs} ${STEAMLIB})
|
||||
message(STATUS "Building for Linux")
|
||||
|
||||
elseif(APPLE)
|
||||
elseif(APPLE)
|
||||
target_sources(skeldal_platform PRIVATE
|
||||
mac_os/save_folder.cpp
|
||||
)
|
||||
|
@ -96,10 +98,10 @@ elseif(APPLE)
|
|||
)
|
||||
target_compile_definitions(mylib PRIVATE PLATFORM_MACOS)
|
||||
set(STEAMLIB ${STEAMWORKS_SDK_DIR}/redistributable_bin/osx/libsteam_api.dylib)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations")
|
||||
message(STATUS "Building for macOS")
|
||||
target_link_libraries(skeldal ${all_libs} ${STEAMLIB})
|
||||
else()
|
||||
else()
|
||||
error("Platform not detected, please add new platform here")
|
||||
endif()
|
||||
set_property(TARGET skeldal_platform PROPERTY CXX_STANDARD 20)
|
||||
|
|
|
@ -19,6 +19,7 @@ void show_help(const char *arg0) {
|
|||
"-a <adv> path for adventure file (.adv)\n"
|
||||
"-l <lang> set language (cz|en)\n"
|
||||
"-s <directory> generate string-tables (for localization) and exit\n"
|
||||
"-L <host:port> connect to host:port to listen commands (mapedit)\n"
|
||||
"-h this help\n");
|
||||
exit(1);
|
||||
}
|
||||
|
@ -33,13 +34,15 @@ int main(int argc, char **argv) {
|
|||
std::string adv_config_file;
|
||||
std::string gen_stringtable_path;
|
||||
std::string lang;
|
||||
for (int optchr = -1; (optchr = getopt(argc, argv, "hf:a:s:l:")) != -1; ) {
|
||||
std::string sse_hostport;
|
||||
for (int optchr = -1; (optchr = getopt(argc, argv, "hf:a:s:l:L:")) != -1; ) {
|
||||
switch (optchr) {
|
||||
case 'f': config_name = optarg;break;
|
||||
case 'a': adv_config_file = optarg;break;
|
||||
case 'h': show_help(argv[0]);break;
|
||||
case 'l': lang = optarg;break;
|
||||
case 's': gen_stringtable_path = optarg;break;
|
||||
case 'L': sse_hostport = optarg;break;
|
||||
default: show_help_short();
|
||||
return 1;
|
||||
}
|
||||
|
@ -53,6 +56,7 @@ int main(int argc, char **argv) {
|
|||
cfg.adventure_path = adv_config_file.empty()?NULL:adv_config_file.c_str();
|
||||
cfg.config_path = config_name.c_str();
|
||||
cfg.lang_path = lang.empty()?NULL:lang.c_str();
|
||||
cfg.sse_hostport = sse_hostport.c_str();
|
||||
try {
|
||||
|
||||
if (!gen_stringtable_path.empty()) {
|
||||
|
|
44
platform/mtqueue.cpp
Normal file
44
platform/mtqueue.cpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
#include "mtqueue.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <malloc.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
struct StringDeleter {
|
||||
void operator()(char *x) {
|
||||
free(x);
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<char, StringDeleter> alloc_string(const char *x) {
|
||||
return std::unique_ptr<char, StringDeleter>(strdup(x));
|
||||
}
|
||||
|
||||
|
||||
typedef struct tag_mtqueue {
|
||||
std::queue<std::unique_ptr<char, StringDeleter> > _q;
|
||||
std::mutex _mx;
|
||||
|
||||
|
||||
} MTQUEUE;
|
||||
|
||||
MTQUEUE *mtqueue_create() {
|
||||
return new MTQUEUE();
|
||||
}
|
||||
void mtqueue_push(MTQUEUE *q, const char *message) {
|
||||
std::lock_guard _(q->_mx);
|
||||
q->_q.push(alloc_string(message));
|
||||
}
|
||||
char *mtqueue_pop(MTQUEUE *q) {
|
||||
std::lock_guard _(q->_mx);
|
||||
if (q->_q.empty()) return NULL;
|
||||
else {
|
||||
char *c = q->_q.front().release();
|
||||
q->_q.pop();
|
||||
return c;
|
||||
}
|
||||
}
|
||||
void mtqueue_destroy(MTQUEUE *q) {
|
||||
delete q;
|
||||
}
|
32
platform/mtqueue.h
Normal file
32
platform/mtqueue.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct tag_mtqueue MTQUEUE;
|
||||
///Create multithread queue
|
||||
MTQUEUE *mtqueue_create();
|
||||
///push to queue (string is copied)
|
||||
/**
|
||||
* @param q queue
|
||||
* @param message message (string is copied)
|
||||
*/
|
||||
void mtqueue_push(MTQUEUE *q, const char *message);
|
||||
///pop from the queue
|
||||
/**
|
||||
*
|
||||
* @param q queue
|
||||
* @return NULL, if queue is empty, or string. You have to release
|
||||
* string by calling free() when you finish.
|
||||
*/
|
||||
char *mtqueue_pop(MTQUEUE *q);
|
||||
|
||||
///destroy the queue
|
||||
void mtqueue_destroy(MTQUEUE *q);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
142
platform/sse_receiver.cpp
Normal file
142
platform/sse_receiver.cpp
Normal file
|
@ -0,0 +1,142 @@
|
|||
#include "sse_receiver.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <functional>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
typedef SOCKET sock_t;
|
||||
#define CLOSESOCK closesocket
|
||||
#define sock_init() { WSADATA wsa; WSAStartup(MAKEWORD(2,2), &wsa); }
|
||||
#define sock_cleanup() WSACleanup()
|
||||
#define SHUT_RD SD_RECEIVE
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <fcntl.h>
|
||||
typedef int sock_t;
|
||||
#define INVALID_SOCKET -1
|
||||
#define CLOSESOCK close
|
||||
#define sock_init()
|
||||
#define sock_cleanup()
|
||||
#endif
|
||||
|
||||
#define BUFFER_SIZE 1024
|
||||
|
||||
|
||||
void sse_client_loop(const char *host, const char *port, std::function<void(const char *)> callback, std::stop_token tkn) {
|
||||
sock_init();
|
||||
|
||||
struct addrinfo hints = {}, *res = NULL;
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
if (getaddrinfo(host, port, &hints, &res) != 0) {
|
||||
perror("getaddrinfo");
|
||||
return;
|
||||
}
|
||||
|
||||
sock_t sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
||||
if (sock == INVALID_SOCKET) {
|
||||
perror("socket");
|
||||
freeaddrinfo(res);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (connect(sock, res->ai_addr, res->ai_addrlen) != 0) {
|
||||
perror("connect");
|
||||
CLOSESOCK(sock);
|
||||
freeaddrinfo(res);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
freeaddrinfo(res);
|
||||
|
||||
{
|
||||
std::stop_callback _cb(tkn, [&]{
|
||||
shutdown(sock, SHUT_RD);
|
||||
});
|
||||
|
||||
// Send HTTP GET request
|
||||
char req[512];
|
||||
snprintf(req, sizeof(req),
|
||||
"GET /command HTTP/1.1\r\n"
|
||||
"Host: %s\r\n"
|
||||
"Accept: text/event-stream\r\n"
|
||||
"Connection: keep-alive\r\n\r\n", host);
|
||||
send(sock, req, strlen(req), 0);
|
||||
|
||||
// Read response and extract "data: " lines
|
||||
char buffer[BUFFER_SIZE];
|
||||
int buf_len = 0;
|
||||
|
||||
while (!tkn.stop_requested()) {
|
||||
int n = recv(sock, buffer + buf_len, BUFFER_SIZE - buf_len - 1, 0);
|
||||
if (n <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
buf_len += n;
|
||||
buffer[buf_len] = '\0';
|
||||
|
||||
char *line_start = buffer;
|
||||
while (1) {
|
||||
char *newline = strstr(line_start, "\n");
|
||||
if (!newline) break;
|
||||
|
||||
*newline = '\0';
|
||||
|
||||
if (strncmp(line_start, "data: ", 6) == 0) {
|
||||
callback(line_start + 6);
|
||||
}
|
||||
|
||||
line_start = newline + 1;
|
||||
}
|
||||
|
||||
// Move leftover data to start
|
||||
buf_len = strlen(line_start);
|
||||
memmove(buffer, line_start, buf_len);
|
||||
}
|
||||
|
||||
}
|
||||
CLOSESOCK(sock);
|
||||
sock_cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
typedef struct tag_sse_receiver {
|
||||
std::jthread thr;
|
||||
}
|
||||
SSE_RECEIVER;
|
||||
|
||||
SSE_RECEIVER *sse_receiver_install(MTQUEUE *q, const char *host, const char *port) {
|
||||
SSE_RECEIVER *sse = new SSE_RECEIVER;
|
||||
sse->thr = std::jthread([sse, q, host = std::string(host), port = std::string(port)](std::stop_token tkn){
|
||||
while (!tkn.stop_requested()) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
sse_client_loop(host.c_str(), port.c_str(), [q](const char *msg){
|
||||
mtqueue_push(q, msg);
|
||||
}, tkn);
|
||||
}
|
||||
});
|
||||
return sse;
|
||||
}
|
||||
void sse_receiver_stop(SSE_RECEIVER *inst) {
|
||||
delete inst;
|
||||
}
|
||||
|
30
platform/sse_receiver.h
Normal file
30
platform/sse_receiver.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include "mtqueue.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct tag_sse_receiver SSE_RECEIVER;
|
||||
|
||||
///Install sse receiver
|
||||
/**
|
||||
* @param q mtqueue, which receives messages received by the receiver
|
||||
* @param host host
|
||||
* @param port port
|
||||
* @return pointer to instance of receiver
|
||||
*/
|
||||
SSE_RECEIVER *sse_receiver_install(MTQUEUE *q, const char *host, const char *port);
|
||||
|
||||
///Stops the receiver
|
||||
/**
|
||||
* @param inst instance of receiver
|
||||
* @note the associated queue is not destroyed
|
||||
*/
|
||||
void sse_receiver_stop(SSE_RECEIVER *inst);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -10,7 +10,7 @@
|
|||
#include <shellapi.h>
|
||||
|
||||
void show_help(std::ostream &out, const char *arg0) {
|
||||
out <<
|
||||
out <<
|
||||
"Brany Skeldalu (Gates of Skeldal) portable game player\n"
|
||||
"Copyright (c) 2025 Ondrej Novak. All rights reserved.\n\n"
|
||||
"This work is licensed under the terms of the MIT license.\n"
|
||||
|
@ -22,7 +22,8 @@ void show_help(std::ostream &out, const char *arg0) {
|
|||
"-a <adv> path for adventure file (.adv)\n"
|
||||
"-l <lang> set language (cz|en)\n"
|
||||
"-s <directory> generate string-tables (for localization) and exit\n"
|
||||
"-h this help\n";
|
||||
"-L <host:port> connect to host:port to listen commands (mapedit)\n"
|
||||
"-h this help\n";
|
||||
}
|
||||
|
||||
void show_help_short(std::ostream &out) {
|
||||
|
@ -30,20 +31,22 @@ void show_help_short(std::ostream &out) {
|
|||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int main(int argc, char **argv) {
|
||||
std::string config_name = SKELDALINI;
|
||||
std::string adv_config_file;
|
||||
std::string gen_stringtable_path;
|
||||
std::string lang;
|
||||
std::string sse_hostport;
|
||||
std::ostringstream console;
|
||||
for (int optchr = -1; (optchr = getopt(argc, argv, "hf:a:s:l:")) != -1; ) {
|
||||
for (int optchr = -1; (optchr = getopt(argc, argv, "hf:a:s:l:L:")) != -1; ) {
|
||||
switch (optchr) {
|
||||
case 'f': config_name = optarg;break;
|
||||
case 'a': adv_config_file = optarg;break;
|
||||
case 'h': show_help(console, argv[0]);break;
|
||||
case 'l': lang = optarg;break;
|
||||
case 's': gen_stringtable_path = optarg;break;
|
||||
default: show_help_short(console);break;
|
||||
case 'L': sse_hostport = optarg;break;
|
||||
default: show_help_short(console);break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +55,7 @@ int main(int argc, char **argv) {
|
|||
show_help(console, argv[0]);
|
||||
}
|
||||
|
||||
SKELDAL_CONFIG cfg;
|
||||
SKELDAL_CONFIG cfg = {};
|
||||
cfg.short_help = []{};
|
||||
cfg.show_error = [](const char *txt) {
|
||||
char buff[MAX_PATH];
|
||||
|
@ -63,6 +66,7 @@ int main(int argc, char **argv) {
|
|||
cfg.adventure_path = adv_config_file.empty()?NULL:adv_config_file.c_str();
|
||||
cfg.config_path = config_name.c_str();
|
||||
cfg.lang_path = lang.empty()?NULL:lang.c_str();
|
||||
cfg.sse_hostport = sse_hostport.c_str();
|
||||
|
||||
{
|
||||
std::string msg = console.str();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue