Skip to content

Commit 1805288

Browse files
authored
add Game::SpawnAsync (#3109)
* add EncodeUTF8String and DecodeUTF8String which use std::string * add Game::SpawnAsync * use Game::SpawnAsync in BUTTON_BOT_START
1 parent 0878717 commit 1805288

5 files changed

Lines changed: 101 additions & 47 deletions

File tree

gframe/bufferio.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <cstdint>
55
#include <cstring>
66
#include <cwchar>
7+
#include <string>
78
#include <vector>
89

910
class BufferIO {
@@ -122,6 +123,20 @@ class BufferIO {
122123
return true;
123124
}
124125
// UTF-16/UTF-32 to UTF-8
126+
static std::string EncodeUTF8String(const std::wstring& wstr) {
127+
if (wstr.empty())
128+
return std::string();
129+
std::mbstate_t state{};
130+
const wchar_t* src = wstr.c_str();
131+
size_t len = std::wcsrtombs(nullptr, &src, 0, &state);
132+
if (len == static_cast<size_t>(-1))
133+
return std::string();
134+
std::string result(len, '\0');
135+
state = std::mbstate_t{};
136+
src = wstr.c_str();
137+
std::wcsrtombs(&result[0], &src, len, &state);
138+
return result;
139+
}
125140
// return: string length
126141
static int EncodeUTF8String(const wchar_t* wsrc, char* str, size_t size) {
127142
if (size == 0) {
@@ -135,6 +150,20 @@ class BufferIO {
135150
return static_cast<int>(result_len);
136151
}
137152
// UTF-8 to UTF-16/UTF-32
153+
static std::wstring DecodeUTF8String(const std::string& str) {
154+
if (str.empty())
155+
return std::wstring();
156+
std::mbstate_t state{};
157+
const char* src = str.c_str();
158+
size_t len = std::mbsrtowcs(nullptr, &src, 0, &state);
159+
if (len == static_cast<size_t>(-1))
160+
return std::wstring();
161+
std::wstring result(len, L'\0');
162+
state = std::mbstate_t{};
163+
src = str.c_str();
164+
std::mbsrtowcs(&result[0], &src, len, &state);
165+
return result;
166+
}
138167
// return: string length
139168
static int DecodeUTF8String(const char* src, wchar_t* wstr, size_t size) {
140169
if (size == 0) {

gframe/game.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@
1414
#include <chrono>
1515
#ifdef _WIN32
1616
#include <timeapi.h>
17+
#else
18+
#include <spawn.h>
19+
#ifdef __APPLE__
20+
#include <crt_externs.h>
21+
#define GetEnviron() (*_NSGetEnviron())
22+
#else
23+
extern char **environ;
24+
#define GetEnviron() environ
25+
#endif
1726
#endif
1827
#ifdef __APPLE__
1928
#include <CoreFoundation/CoreFoundation.h>
@@ -2405,5 +2414,50 @@ void Game::SetCursor(irr::gui::ECURSOR_ICON icon) {
24052414
cursor->setActiveIcon(icon);
24062415
}
24072416
}
2417+
bool Game::SpawnAsync(const std::wstring& exePath, const std::vector<std::wstring>& args) {
2418+
#ifdef _WIN32
2419+
std::wstring cmdLine = L"\"" + exePath + L"\"";
2420+
for (const auto& arg : args) {
2421+
cmdLine += L" \"" + arg + L"\"";
2422+
}
2423+
2424+
STARTUPINFOW si;
2425+
PROCESS_INFORMATION pi;
2426+
ZeroMemory(&si, sizeof(si));
2427+
si.cb = sizeof(si);
2428+
ZeroMemory(&pi, sizeof(pi));
2429+
2430+
// CreateProcessW can modify the command line buffer, so we need to create a mutable copy of it
2431+
// TODO: Move to C++17 and use cmdLine.data() directly without copying to a vector
2432+
std::vector<wchar_t> cmdBuffer(cmdLine.begin(), cmdLine.end());
2433+
cmdBuffer.push_back(L'\0');
2434+
2435+
if (!CreateProcessW(exePath.c_str(), cmdBuffer.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) {
2436+
return false;
2437+
}
2438+
2439+
CloseHandle(pi.hThread);
2440+
CloseHandle(pi.hProcess);
2441+
return true;
2442+
#else
2443+
std::string exePathUTF8 = BufferIO::EncodeUTF8String(exePath);
2444+
2445+
std::vector<std::string> utf8Args;
2446+
utf8Args.emplace_back(exePathUTF8);
2447+
for (const auto& arg : args) {
2448+
utf8Args.push_back(BufferIO::EncodeUTF8String(arg));
2449+
}
2450+
2451+
std::vector<char*> execArgs;
2452+
execArgs.reserve(utf8Args.size() + 1);
2453+
for (auto& arg : utf8Args) {
2454+
execArgs.push_back(const_cast<char*>(arg.c_str()));
2455+
}
2456+
execArgs.push_back(nullptr);
2457+
2458+
pid_t pid{}; // ignore pid return value, use SIG_IGN to prevent zombie process
2459+
return posix_spawn(&pid, exePathUTF8.c_str(), nullptr, nullptr, execArgs.data(), GetEnviron()) == 0;
2460+
#endif
2461+
}
24082462

24092463
}

gframe/game.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ class Game {
265265
void SetWindowsScale(float scale);
266266
void FlashWindow();
267267
void SetCursor(irr::gui::ECURSOR_ICON icon);
268+
269+
static bool SpawnAsync(const std::wstring& exePath, const std::vector<std::wstring>& args);
270+
268271
template<typename T>
269272
static void DrawShadowText(irr::gui::CGUITTFont* font, const T& text, const irr::core::rect<irr::s32>& position, const irr::core::rect<irr::s32>& padding,
270273
irr::video::SColor color = 0xffffffff, irr::video::SColor shadowcolor = 0xff000000, bool hcenter = false, bool vcenter = false, const irr::core::rect<irr::s32>* clip = nullptr);

gframe/gframe.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
#include <event2/thread.h>
55
#include <clocale>
66
#include <memory>
7+
#ifndef _WIN32
8+
#include <signal.h>
9+
#endif
710

811
#if defined(_WIN32) && (!defined(WDK_NTDDI_VERSION) || (WDK_NTDDI_VERSION < 0x0A000005)) // Redstone 4, Version 1803, Build 17134.
912
#error "This program requires the Windows 10 SDK version 1803 or above to compile on Windows. Otherwise, non-ASCII characters will not be displayed or processed correctly."
@@ -48,6 +51,7 @@ int main(int argc, char* argv[]) {
4851
evthread_use_windows_threads();
4952
#else
5053
evthread_use_pthreads();
54+
signal(SIGCHLD, SIG_IGN);
5155
#endif //_WIN32
5256
ygo::Game _game;
5357
ygo::mainGame = &_game;

gframe/menu_handler.cpp

Lines changed: 11 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,6 @@ bool MenuHandler::OnEvent(const irr::SEvent& event) {
319319
if(sel == -1)
320320
break;
321321
mainGame->bot_mode = true;
322-
#ifdef _WIN32
323322
if(!NetServer::StartServer(mainGame->gameConf.serverport)) {
324323
soundManager.PlaySoundEffect(SOUND_INFO);
325324
mainGame->env->addMessageBox(L"", dataManager.GetSysString(1402));
@@ -331,12 +330,7 @@ bool MenuHandler::OnEvent(const irr::SEvent& event) {
331330
mainGame->env->addMessageBox(L"", dataManager.GetSysString(1402));
332331
break;
333332
}
334-
STARTUPINFOW si;
335-
PROCESS_INFORMATION pi;
336-
ZeroMemory(&si, sizeof(si));
337-
si.cb = sizeof(si);
338-
ZeroMemory(&pi, sizeof(pi));
339-
wchar_t cmd[MAX_PATH];
333+
std::vector<std::wstring> processArgs;
340334
wchar_t arg1[512];
341335
if(mainGame->botInfo[sel].select_deckfile) {
342336
wchar_t botdeck[256];
@@ -345,51 +339,21 @@ bool MenuHandler::OnEvent(const irr::SEvent& event) {
345339
}
346340
else
347341
myswprintf(arg1, L"%ls", mainGame->botInfo[sel].command);
342+
processArgs.push_back(arg1);
348343
int flag = 0;
349344
flag += (mainGame->chkBotHand->isChecked() ? 0x1 : 0);
350-
myswprintf(cmd, L"Bot.exe \"%ls\" %d %d", arg1, flag, mainGame->gameConf.serverport);
351-
if(!CreateProcessW(nullptr, cmd, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi))
352-
{
345+
processArgs.push_back(std::to_wstring(flag));
346+
processArgs.push_back(std::to_wstring(mainGame->gameConf.serverport));
347+
#ifdef _WIN32
348+
std::wstring executableName = L"Bot.exe";
349+
#else
350+
std::wstring executableName = L"./bot";
351+
#endif
352+
if (!Game::SpawnAsync(executableName, processArgs)) {
353+
DuelClient::StopClient();
353354
NetServer::StopServer();
354355
break;
355356
}
356-
CloseHandle(pi.hThread);
357-
CloseHandle(pi.hProcess);
358-
#else
359-
if(fork() == 0) {
360-
usleep(100000);
361-
wchar_t warg1[512];
362-
if(mainGame->botInfo[sel].select_deckfile) {
363-
wchar_t botdeck[256];
364-
DeckManager::GetDeckFile(botdeck, mainGame->cbBotDeckCategory->getSelected(), mainGame->cbBotDeckCategory->getText(), mainGame->cbBotDeck->getText());
365-
myswprintf(warg1, L"%ls DeckFile='%ls'", mainGame->botInfo[sel].command, botdeck);
366-
}
367-
else
368-
myswprintf(warg1, L"%ls", mainGame->botInfo[sel].command);
369-
char arg1[512];
370-
BufferIO::EncodeUTF8(warg1, arg1);
371-
int flag = 0;
372-
flag += (mainGame->chkBotHand->isChecked() ? 0x1 : 0);
373-
char arg2[8];
374-
mysnprintf(arg2, "%d", flag);
375-
char arg3[8];
376-
mysnprintf(arg3, "%d", mainGame->gameConf.serverport);
377-
execl("./bot", "bot", arg1, arg2, arg3, nullptr);
378-
std::exit(0);
379-
} else {
380-
if(!NetServer::StartServer(mainGame->gameConf.serverport)) {
381-
soundManager.PlaySoundEffect(SOUND_INFO);
382-
mainGame->env->addMessageBox(L"", dataManager.GetSysString(1402));
383-
break;
384-
}
385-
if(!DuelClient::StartClient(0x7f000001, mainGame->gameConf.serverport)) {
386-
NetServer::StopServer();
387-
soundManager.PlaySoundEffect(SOUND_INFO);
388-
mainGame->env->addMessageBox(L"", dataManager.GetSysString(1402));
389-
break;
390-
}
391-
}
392-
#endif
393357
mainGame->btnStartBot->setEnabled(false);
394358
mainGame->btnBotCancel->setEnabled(false);
395359
break;

0 commit comments

Comments
 (0)