mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-03-22 07:48:13 +05:00
Fix server DedicatedServer issues (#1266)
* add: Dedicated Server implementation - Introduced `ServerMain.cpp` for the dedicated server logic, handling command-line arguments, server initialization, and network management. - Created `postbuild_server.ps1` script for post-build tasks, including copying necessary resources and DLLs for the dedicated server. - Added `CopyServerAssets.cmake` to manage the copying of server assets during the build process, ensuring required files are available for the dedicated server. - Defined project filters in `Minecraft.Server.vcxproj.filters` for better organization of server-related files. * add: refactor world loader & add server properties - Introduced ServerLogger for logging startup steps and world I/O operations. - Implemented ServerProperties for loading and saving server configuration from `server.properties`. - Added WorldManager to handle world loading and creation based on server properties. - Updated ServerMain to integrate server properties loading and world management. - Enhanced project files to include new source and header files for the server components. * update: implement enhanced logging functionality with configurable log levels * update: update keyboard and mouse input initialization1dc8a005ed* fix: change virtual screen resolution to 1920x1080(HD) Since 31881af56936aeef38ff322b975fd0 , `skinHud.swf` for 720 is not included in `MediaWindows64.arc`, the app crashes unless the virtual screen is set to HD. * fix: dedicated server build settings for miniaudio migration and missing sources - remove stale Windows64 Miles (mss64) link/copy references from server build - add Common/Filesystem/Filesystem.cpp to Minecraft.Server.vcxproj - add Windows64/PostProcesser.cpp to Minecraft.Server.vcxproj - fix unresolved externals (PostProcesser::*, FileExists) in dedicated server build * update: changed the virtual screen to 720p Since the crash caused by the 720p `skinHud.swf` not being included in `MediaWindows64.arc` has been resolved, switching back to 720p to reduce resource usage. * add: add Docker support for Dedicated Server add with entrypoint and build scripts * fix: add initial save for newly created worlds in dedicated server on the server side, I fixed the behavior introduced after commitaadb511, where newly created worlds are intentionally not saved to disk immediately. * update: add basically all configuration options that are implemented in the classes to `server.properties` * update: add LAN advertising configuration for server.properties LAN-Discovery, which isn’t needed in server mode and could potentially be a security risk, has also been disabled(only server mode). * add: add implementing interactive command line using linenoise - Integrated linenoise library for line editing and completion in the server console. - Updated ServerLogger to handle external writes safely during logging. - Modified ServerMain to initialize and manage the ServerCli for command input. - The implementation is separate from everything else, so it doesn't affect anything else. - The command input section and execution section are separated into threads. * update: enhance command line completion with predictive hints Like most command line tools, it highlights predictions in gray. * add: implement `StringUtils` for string manipulation and refactor usages Unified the scattered utility functions. * fix: send DisconnectPacket on shutdown and fix Win64 recv-thread teardown race Before this change, server/host shutdown closed sockets directly in ServerConnection::stop(), which bypassed the normal disconnect flow. As a result, clients could be dropped without receiving a proper DisconnectPacket during stop/kill/world-close paths. Also, WinsockNetLayer::Shutdown() could destroy synchronization objects while host-side recv threads were still exiting, causing a crash in RecvThreadProc (access violation on world close in host mode). * fix: return client to menus when Win64 host connection drops - Add client-side host disconnect handling in CPlatformNetworkManagerStub::DoWork() for _WINDOWS64. - When in QNET_STATE_GAME_PLAY as a non-host and WinsockNetLayer::IsConnected() becomes false, trigger g_NetworkManager.HandleDisconnect(false) to enter the normal disconnect/UI flow. - Use m_bLeaveGameOnTick as a one-shot guard to prevent repeated disconnect handling while the link remains down. - Reset m_bLeaveGameOnTick on LeaveGame(), HostGame(), and JoinGame() to avoid stale state across sessions. * update: converted Japanese comments to English * add: create `Minecraft.Server` developer guide in English and Japanese * update: add note about issue * add: add `nlohmann/json` json lib * add: add FileUtils Moved file operations to `utils`. * add: Dedicated Server BAN access manager with persistent player and IP bans - add Access frontend that publishes thread-safe ban manager snapshots for dedicated server use - add BanManager storage for banned-players.json and banned-ips.json with load/save/update flows - add persistent player and IP ban checks during dedicated server connection handling - add UTF-8 BOM-safe JSON parsing and shared file helpers backed by nlohmann/json - add Unicode-safe ban file read/write and safer atomic replacement behavior on Windows - add active-ban snapshot APIs and expiry-aware filtering for expires metadata - add RAII-based dedicated access shutdown handling during server startup and teardown * update: changed file read/write operations to use `FileUtils`. - As a side effect, saving has become faster! * fix: Re-added the source that had somehow disappeared. * add: significantly improved the dedicated server logging system - add ServerLogManager to Minecraft.Server as the single entry point for dedicated-server log output - forward CMinecraftApp logger output to the server logger when running with g_Win64DedicatedServer - add named network logs for incoming, accepted, rejected, and disconnected connections - cache connection metadata by smallId so player name and remote IP remain available for disconnect logs - keep Minecraft.Client changes minimal by using lightweight hook points and handling log orchestration on the server side * fix: added the updated library source * add: add `ban` and `pardon` commands for Player and IP * fix: fix stop command shutdown process add dedicated server shutdown request handling * fix: fixed the save logic during server shutdown Removed redundant repeated saves and eliminated the risks of async writes. * update: added new sever files to Docker entrypoint * fix: replace shutdown flag with atomic variable for thread safety * update: update Dedicated Server developer guide English is machine translated. Please forgive me. * update: check for the existence of `GameHDD` and create * add: add Whitelist to Dedicated Server * refactor: clean up and refactor the code - unify duplicated implementations that were copied repeatedly - update outdated patterns to more modern ones * fix: include UI header (new update fix) * fix: fix the detection range for excessive logging `getHighestNonEmptyY()` returning `-1` occurs normally when the chunk is entirely air. The caller (`Minecraft.World/LevelChunk.cpp:2400`) normalizes `-1` to `0`. * update: add world size config to dedicated server properties * update: update README add explanation of `server.properties` & launch arguments * update: add nightly release workflow for dedicated server and client builds to Actions * fix: update name for workflow * add random seed generation * add: add Docker nightly workflow for Dedicated Server publish to GitHub Container Registry * fix: ghost player when clients disconnect out of order #4 * fix: fix 7zip option * fix: fix Docker workflow for Dedicated Server artifact handling * add: add no build Dedicated Server startup scripts and Docker Compose * update: add README for Docker Dedicated Server setup with no local build * refactor: refactor command path structure As the number of commands has increased and become harder to navigate, each command has been organized into separate folders. * update: support stream(file stdin) input mode for server CLI Support for the stream (file stdin) required when attaching a tty to a Docker container on Linux. * add: add new CLI Console Commands for Dedicated Server Most of these commands are executed using the command dispatcher implemented on the `Minecraft.World` side. When registering them with the dispatcher, the sender uses a permission-enabled configuration that treats the CLI as a player. - default game. - enchant - experience. - give - kill(currently, getting a permission error for some reason) - time - weather. - update tp & gamemode command * fix: change player map icon to random select * update: increase the player limit * add: restore the basic anti-cheat implementation and add spawn protection Added the following anti-cheat measures and add spawn protection to `server.properties`. - instant break - speed - reach * fix: fix Docker image tag * make chunks delay less for dedi * fix: prevent overwriting allow-flight value on server startup * fix: mitigate entity id overflow and crash for max chunk updates * remove autosave prompt for dedicated server * fix: fix `Failed to create window instance.` Wait for Xvfb to be fully ready before starting. * Revert wrong readme order --------- Co-authored-by: sylvessa <225480449+sylvessa@users.noreply.github.com> Co-authored-by: Loki Rautio <lokirautio@gmail.com>
This commit is contained in:
@@ -657,7 +657,7 @@ bool MinecraftServer::initServer(int64_t seed, NetworkGameInitData *initData, DW
|
|||||||
setFlightAllowed(GetDedicatedServerBool(settings, L"allow-flight", true));
|
setFlightAllowed(GetDedicatedServerBool(settings, L"allow-flight", true));
|
||||||
|
|
||||||
// 4J Stu - Enabling flight to stop it kicking us when we use it
|
// 4J Stu - Enabling flight to stop it kicking us when we use it
|
||||||
#ifdef _DEBUG_MENUS_ENABLED
|
#if (defined _DEBUG_MENUS_ENABLED && defined _DEBUG)
|
||||||
setFlightAllowed(true);
|
setFlightAllowed(true);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1715,330 +1715,345 @@ void MinecraftServer::setPlayerIdleTimeout(int playerIdleTimeout)
|
|||||||
extern int c0a, c0b, c1a, c1b, c1c, c2a, c2b;
|
extern int c0a, c0b, c1a, c1b, c1c, c2a, c2b;
|
||||||
void MinecraftServer::run(int64_t seed, void *lpParameter)
|
void MinecraftServer::run(int64_t seed, void *lpParameter)
|
||||||
{
|
{
|
||||||
NetworkGameInitData *initData = nullptr;
|
NetworkGameInitData *initData = nullptr;
|
||||||
DWORD initSettings = 0;
|
DWORD initSettings = 0;
|
||||||
bool findSeed = false;
|
bool findSeed = false;
|
||||||
if(lpParameter != nullptr)
|
if(lpParameter != nullptr)
|
||||||
{
|
{
|
||||||
initData = static_cast<NetworkGameInitData *>(lpParameter);
|
initData = static_cast<NetworkGameInitData *>(lpParameter);
|
||||||
initSettings = app.GetGameHostOption(eGameHostOption_All);
|
initSettings = app.GetGameHostOption(eGameHostOption_All);
|
||||||
findSeed = initData->findSeed;
|
findSeed = initData->findSeed;
|
||||||
m_texturePackId = initData->texturePackId;
|
m_texturePackId = initData->texturePackId;
|
||||||
}
|
}
|
||||||
// try { // 4J - removed try/catch/finally
|
// try { // 4J - removed try/catch/finally
|
||||||
bool didInit = false;
|
bool didInit = false;
|
||||||
if (initServer(seed, initData, initSettings,findSeed))
|
if (initServer(seed, initData, initSettings,findSeed))
|
||||||
{
|
{
|
||||||
didInit = true;
|
didInit = true;
|
||||||
ServerLevel *levelNormalDimension = levels[0];
|
ServerLevel *levelNormalDimension = levels[0];
|
||||||
// 4J-PB - Set the Stronghold position in the leveldata if there isn't one in there
|
// 4J-PB - Set the Stronghold position in the leveldata if there isn't one in there
|
||||||
Minecraft *pMinecraft = Minecraft::GetInstance();
|
Minecraft *pMinecraft = Minecraft::GetInstance();
|
||||||
LevelData *pLevelData=levelNormalDimension->getLevelData();
|
LevelData *pLevelData=levelNormalDimension->getLevelData();
|
||||||
|
|
||||||
if(pLevelData && pLevelData->getHasStronghold()==false)
|
if(pLevelData && pLevelData->getHasStronghold()==false)
|
||||||
{
|
{
|
||||||
int x,z;
|
int x,z;
|
||||||
if(app.GetTerrainFeaturePosition(eTerrainFeature_Stronghold,&x,&z))
|
if(app.GetTerrainFeaturePosition(eTerrainFeature_Stronghold,&x,&z))
|
||||||
{
|
{
|
||||||
pLevelData->setXStronghold(x);
|
pLevelData->setXStronghold(x);
|
||||||
pLevelData->setZStronghold(z);
|
pLevelData->setZStronghold(z);
|
||||||
pLevelData->setHasStronghold();
|
pLevelData->setHasStronghold();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t lastTime = getCurrentTimeMillis();
|
int64_t lastTime = getCurrentTimeMillis();
|
||||||
int64_t unprocessedTime = 0;
|
int64_t unprocessedTime = 0;
|
||||||
while (running && !s_bServerHalted)
|
while (running && !s_bServerHalted)
|
||||||
{
|
{
|
||||||
int64_t now = getCurrentTimeMillis();
|
int64_t now = getCurrentTimeMillis();
|
||||||
|
|
||||||
// 4J Stu - When we pause the server, we don't want to count that as time passed
|
// 4J Stu - When we pause the server, we don't want to count that as time passed
|
||||||
// 4J Stu - TU-1 hotifx - Remove this line. We want to make sure that we tick connections at the proper rate when paused
|
// 4J Stu - TU-1 hotifx - Remove this line. We want to make sure that we tick connections at the proper rate when paused
|
||||||
//Fix for #13191 - The host of a game can get a message informing them that the connection to the server has been lost
|
//Fix for #13191 - The host of a game can get a message informing them that the connection to the server has been lost
|
||||||
//if(m_isServerPaused) lastTime = now;
|
//if(m_isServerPaused) lastTime = now;
|
||||||
|
|
||||||
int64_t passedTime = now - lastTime;
|
int64_t passedTime = now - lastTime;
|
||||||
if (passedTime > MS_PER_TICK * 40)
|
if (passedTime > MS_PER_TICK * 40)
|
||||||
{
|
{
|
||||||
// logger.warning("Can't keep up! Did the system time change, or is the server overloaded?");
|
// logger.warning("Can't keep up! Did the system time change, or is the server overloaded?");
|
||||||
passedTime = MS_PER_TICK * 40;
|
passedTime = MS_PER_TICK * 40;
|
||||||
}
|
}
|
||||||
if (passedTime < 0)
|
if (passedTime < 0)
|
||||||
{
|
{
|
||||||
// logger.warning("Time ran backwards! Did the system time change?");
|
// logger.warning("Time ran backwards! Did the system time change?");
|
||||||
passedTime = 0;
|
passedTime = 0;
|
||||||
}
|
}
|
||||||
unprocessedTime += passedTime;
|
unprocessedTime += passedTime;
|
||||||
lastTime = now;
|
lastTime = now;
|
||||||
|
|
||||||
// 4J Added ability to pause the server
|
// 4J Added ability to pause the server
|
||||||
if( !m_isServerPaused )
|
if( !m_isServerPaused )
|
||||||
{
|
{
|
||||||
bool didTick = false;
|
bool didTick = false;
|
||||||
if (levels[0]->allPlayersAreSleeping())
|
if (levels[0]->allPlayersAreSleeping())
|
||||||
{
|
{
|
||||||
tick();
|
tick();
|
||||||
unprocessedTime = 0;
|
unprocessedTime = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// int tickcount = 0;
|
// int tickcount = 0;
|
||||||
// int64_t beforeall = System::currentTimeMillis();
|
// int64_t beforeall = System::currentTimeMillis();
|
||||||
while (unprocessedTime > MS_PER_TICK)
|
while (unprocessedTime > MS_PER_TICK)
|
||||||
{
|
{
|
||||||
unprocessedTime -= MS_PER_TICK;
|
unprocessedTime -= MS_PER_TICK;
|
||||||
chunkPacketManagement_PreTick();
|
chunkPacketManagement_PreTick();
|
||||||
// int64_t before = System::currentTimeMillis();
|
// int64_t before = System::currentTimeMillis();
|
||||||
tick();
|
tick();
|
||||||
// int64_t after = System::currentTimeMillis();
|
// int64_t after = System::currentTimeMillis();
|
||||||
// PIXReportCounter(L"Server time",(float)(after-before));
|
// PIXReportCounter(L"Server time",(float)(after-before));
|
||||||
|
|
||||||
chunkPacketManagement_PostTick();
|
chunkPacketManagement_PostTick();
|
||||||
}
|
}
|
||||||
// int64_t afterall = System::currentTimeMillis();
|
// int64_t afterall = System::currentTimeMillis();
|
||||||
// PIXReportCounter(L"Server time all",(float)(afterall-beforeall));
|
// PIXReportCounter(L"Server time all",(float)(afterall-beforeall));
|
||||||
// PIXReportCounter(L"Server ticks",(float)tickcount);
|
// PIXReportCounter(L"Server ticks",(float)tickcount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 4J Stu - TU1-hotfix
|
// 4J Stu - TU1-hotfix
|
||||||
//Fix for #13191 - The host of a game can get a message informing them that the connection to the server has been lost
|
//Fix for #13191 - The host of a game can get a message informing them that the connection to the server has been lost
|
||||||
// The connections should tick at the same frequency even when paused
|
// The connections should tick at the same frequency even when paused
|
||||||
while (unprocessedTime > MS_PER_TICK)
|
while (unprocessedTime > MS_PER_TICK)
|
||||||
{
|
{
|
||||||
unprocessedTime -= MS_PER_TICK;
|
unprocessedTime -= MS_PER_TICK;
|
||||||
// Keep ticking the connections to stop them timing out
|
// Keep ticking the connections to stop them timing out
|
||||||
connection->tick();
|
connection->tick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(MinecraftServer::setTimeAtEndOfTick)
|
if(MinecraftServer::setTimeAtEndOfTick)
|
||||||
{
|
{
|
||||||
MinecraftServer::setTimeAtEndOfTick = false;
|
MinecraftServer::setTimeAtEndOfTick = false;
|
||||||
for (unsigned int i = 0; i < levels.length; i++)
|
for (unsigned int i = 0; i < levels.length; i++)
|
||||||
{
|
{
|
||||||
// if (i == 0 || settings->getBoolean(L"allow-nether", true)) // 4J removed - we always have nether
|
// if (i == 0 || settings->getBoolean(L"allow-nether", true)) // 4J removed - we always have nether
|
||||||
{
|
{
|
||||||
ServerLevel *level = levels[i];
|
ServerLevel *level = levels[i];
|
||||||
level->setGameTime( MinecraftServer::setTime );
|
level->setGameTime( MinecraftServer::setTime );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(MinecraftServer::setTimeOfDayAtEndOfTick)
|
if(MinecraftServer::setTimeOfDayAtEndOfTick)
|
||||||
{
|
{
|
||||||
MinecraftServer::setTimeOfDayAtEndOfTick = false;
|
MinecraftServer::setTimeOfDayAtEndOfTick = false;
|
||||||
for (unsigned int i = 0; i < levels.length; i++)
|
for (unsigned int i = 0; i < levels.length; i++)
|
||||||
{
|
{
|
||||||
if (i == 0 || GetDedicatedServerBool(settings, L"allow-nether", true))
|
if (i == 0 || GetDedicatedServerBool(settings, L"allow-nether", true))
|
||||||
{
|
{
|
||||||
ServerLevel *level = levels[i];
|
ServerLevel *level = levels[i];
|
||||||
level->setDayTime( MinecraftServer::setTimeOfDay );
|
level->setDayTime( MinecraftServer::setTimeOfDay );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process delayed actions
|
// Process delayed actions
|
||||||
eXuiServerAction eAction;
|
eXuiServerAction eAction;
|
||||||
LPVOID param;
|
LPVOID param;
|
||||||
for(int i=0;i<XUSER_MAX_COUNT;i++)
|
for(int i=0;i<XUSER_MAX_COUNT;i++)
|
||||||
{
|
{
|
||||||
eAction = app.GetXuiServerAction(i);
|
eAction = app.GetXuiServerAction(i);
|
||||||
param = app.GetXuiServerActionParam(i);
|
param = app.GetXuiServerActionParam(i);
|
||||||
|
|
||||||
switch(eAction)
|
switch(eAction)
|
||||||
{
|
{
|
||||||
case eXuiServerAction_AutoSaveGame:
|
case eXuiServerAction_AutoSaveGame:
|
||||||
|
#if defined(_XBOX_ONE) || defined(__ORBIS__) || defined(MINECRAFT_SERVER_BUILD)
|
||||||
|
{
|
||||||
#if defined(_XBOX_ONE) || defined(__ORBIS__)
|
#if defined(_XBOX_ONE) || defined(__ORBIS__)
|
||||||
{
|
PIXBeginNamedEvent(0, "Autosave");
|
||||||
PIXBeginNamedEvent(0,"Autosave");
|
|
||||||
|
|
||||||
// Get the frequency of the timer
|
// Get the frequency of the timer
|
||||||
LARGE_INTEGER qwTicksPerSec, qwTime, qwNewTime, qwDeltaTime;
|
LARGE_INTEGER qwTicksPerSec, qwTime, qwNewTime, qwDeltaTime;
|
||||||
float fElapsedTime = 0.0f;
|
float fElapsedTime = 0.0f;
|
||||||
QueryPerformanceFrequency( &qwTicksPerSec );
|
QueryPerformanceFrequency(&qwTicksPerSec);
|
||||||
float fSecsPerTick = 1.0f / (float)qwTicksPerSec.QuadPart;
|
float fSecsPerTick = 1.0f / (float)qwTicksPerSec.QuadPart;
|
||||||
|
|
||||||
// Save the start time
|
// Save the start time
|
||||||
QueryPerformanceCounter( &qwTime );
|
QueryPerformanceCounter(&qwTime);
|
||||||
|
|
||||||
if (players != nullptr)
|
|
||||||
{
|
|
||||||
players->saveAll(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (unsigned int j = 0; j < levels.length; j++)
|
|
||||||
{
|
|
||||||
if( s_bServerHalted ) break;
|
|
||||||
// 4J Stu - Save the levels in reverse order so we don't overwrite the level.dat
|
|
||||||
// with the data from the nethers leveldata.
|
|
||||||
// Fix for #7418 - Functional: Gameplay: Saving after sleeping in a bed will place player at nighttime when restarting.
|
|
||||||
ServerLevel *level = levels[levels.length - 1 - j];
|
|
||||||
PIXBeginNamedEvent(0, "Saving level %d",levels.length - 1 - j);
|
|
||||||
level->save(false, nullptr, true);
|
|
||||||
PIXEndNamedEvent();
|
|
||||||
}
|
|
||||||
if( !s_bServerHalted )
|
|
||||||
{
|
|
||||||
PIXBeginNamedEvent(0,"Saving game rules");
|
|
||||||
saveGameRules();
|
|
||||||
PIXEndNamedEvent();
|
|
||||||
|
|
||||||
PIXBeginNamedEvent(0,"Save to disc");
|
|
||||||
levels[0]->saveToDisc(Minecraft::GetInstance()->progressRenderer, true);
|
|
||||||
PIXEndNamedEvent();
|
|
||||||
}
|
|
||||||
PIXEndNamedEvent();
|
|
||||||
|
|
||||||
QueryPerformanceCounter( &qwNewTime );
|
|
||||||
qwDeltaTime.QuadPart = qwNewTime.QuadPart - qwTime.QuadPart;
|
|
||||||
fElapsedTime = fSecsPerTick * ((FLOAT)(qwDeltaTime.QuadPart));
|
|
||||||
app.DebugPrintf("Autosave: Elapsed time %f\n", fElapsedTime);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
#endif
|
#endif
|
||||||
case eXuiServerAction_SaveGame:
|
|
||||||
app.EnterSaveNotificationSection();
|
|
||||||
if (players != nullptr)
|
|
||||||
{
|
|
||||||
players->saveAll(Minecraft::GetInstance()->progressRenderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
players->broadcastAll(std::make_shared<UpdateProgressPacket>(20));
|
if (players != nullptr)
|
||||||
|
{
|
||||||
|
players->saveAll(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
for (unsigned int j = 0; j < levels.length; j++)
|
for (unsigned int j = 0; j < levels.length; j++)
|
||||||
{
|
{
|
||||||
|
if( s_bServerHalted ) break;
|
||||||
|
// 4J Stu - Save the levels in reverse order so we don't overwrite the level.dat
|
||||||
|
// with the data from the nethers leveldata.
|
||||||
|
// Fix for #7418 - Functional: Gameplay: Saving after sleeping in a bed will place player at nighttime when restarting.
|
||||||
|
ServerLevel *level = levels[levels.length - 1 - j];
|
||||||
|
#if defined(_XBOX_ONE) || defined(__ORBIS__)
|
||||||
|
PIXBeginNamedEvent(0, "Saving level %d", levels.length - 1 - j);
|
||||||
|
#endif
|
||||||
|
level->save(false, nullptr, true);
|
||||||
|
#if defined(_XBOX_ONE) || defined(__ORBIS__)
|
||||||
|
PIXEndNamedEvent();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
if (!s_bServerHalted)
|
||||||
|
{
|
||||||
|
#if defined(_XBOX_ONE) || defined(__ORBIS__)
|
||||||
|
PIXBeginNamedEvent(0, "Saving game rules");
|
||||||
|
#endif
|
||||||
|
saveGameRules();
|
||||||
|
#if defined(_XBOX_ONE) || defined(__ORBIS__)
|
||||||
|
PIXEndNamedEvent();
|
||||||
|
|
||||||
|
PIXBeginNamedEvent(0, "Save to disc");
|
||||||
|
#endif
|
||||||
|
levels[0]->saveToDisc(Minecraft::GetInstance()->progressRenderer, true);
|
||||||
|
#if defined(_XBOX_ONE) || defined(__ORBIS__)
|
||||||
|
PIXEndNamedEvent();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(_XBOX_ONE) || defined(__ORBIS__)
|
||||||
|
PIXEndNamedEvent();
|
||||||
|
|
||||||
|
QueryPerformanceCounter(&qwNewTime);
|
||||||
|
qwDeltaTime.QuadPart = qwNewTime.QuadPart - qwTime.QuadPart;
|
||||||
|
fElapsedTime = fSecsPerTick * ((FLOAT)(qwDeltaTime.QuadPart));
|
||||||
|
app.DebugPrintf("Autosave: Elapsed time %f\n", fElapsedTime);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
case eXuiServerAction_SaveGame:
|
||||||
|
app.EnterSaveNotificationSection();
|
||||||
|
if (players != nullptr)
|
||||||
|
{
|
||||||
|
players->saveAll(Minecraft::GetInstance()->progressRenderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
players->broadcastAll(std::make_shared<UpdateProgressPacket>(20));
|
||||||
|
|
||||||
|
for (unsigned int j = 0; j < levels.length; j++)
|
||||||
|
{
|
||||||
if( s_bServerHalted ) break;
|
if( s_bServerHalted ) break;
|
||||||
// 4J Stu - Save the levels in reverse order so we don't overwrite the level.dat
|
// 4J Stu - Save the levels in reverse order so we don't overwrite the level.dat
|
||||||
// with the data from the nethers leveldata.
|
// with the data from the nethers leveldata.
|
||||||
// Fix for #7418 - Functional: Gameplay: Saving after sleeping in a bed will place player at nighttime when restarting.
|
// Fix for #7418 - Functional: Gameplay: Saving after sleeping in a bed will place player at nighttime when restarting.
|
||||||
ServerLevel *level = levels[levels.length - 1 - j];
|
ServerLevel *level = levels[levels.length - 1 - j];
|
||||||
level->save(true, Minecraft::GetInstance()->progressRenderer, (eAction==eXuiServerAction_AutoSaveGame));
|
level->save(true, Minecraft::GetInstance()->progressRenderer, (eAction==eXuiServerAction_AutoSaveGame));
|
||||||
|
|
||||||
players->broadcastAll(std::make_shared<UpdateProgressPacket>(33 + (j * 33)));
|
players->broadcastAll(std::make_shared<UpdateProgressPacket>(33 + (j * 33)));
|
||||||
}
|
}
|
||||||
if( !s_bServerHalted )
|
if( !s_bServerHalted )
|
||||||
{
|
{
|
||||||
saveGameRules();
|
saveGameRules();
|
||||||
|
|
||||||
levels[0]->saveToDisc(Minecraft::GetInstance()->progressRenderer, (eAction==eXuiServerAction_AutoSaveGame));
|
levels[0]->saveToDisc(Minecraft::GetInstance()->progressRenderer, (eAction==eXuiServerAction_AutoSaveGame));
|
||||||
}
|
}
|
||||||
app.LeaveSaveNotificationSection();
|
app.LeaveSaveNotificationSection();
|
||||||
break;
|
break;
|
||||||
case eXuiServerAction_DropItem:
|
case eXuiServerAction_DropItem:
|
||||||
// Find the player, and drop the id at their feet
|
// Find the player, and drop the id at their feet
|
||||||
{
|
{
|
||||||
shared_ptr<ServerPlayer> player = players->players.at(0);
|
shared_ptr<ServerPlayer> player = players->players.at(0);
|
||||||
size_t id = (size_t) param;
|
size_t id = (size_t) param;
|
||||||
player->drop(std::make_shared<ItemInstance>(id, 1, 0));
|
player->drop(std::make_shared<ItemInstance>(id, 1, 0));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case eXuiServerAction_SpawnMob:
|
case eXuiServerAction_SpawnMob:
|
||||||
{
|
{
|
||||||
shared_ptr<ServerPlayer> player = players->players.at(0);
|
shared_ptr<ServerPlayer> player = players->players.at(0);
|
||||||
eINSTANCEOF factory = static_cast<eINSTANCEOF>((size_t)param);
|
eINSTANCEOF factory = static_cast<eINSTANCEOF>((size_t)param);
|
||||||
shared_ptr<Mob> mob = dynamic_pointer_cast<Mob>(EntityIO::newByEnumType(factory,player->level ));
|
shared_ptr<Mob> mob = dynamic_pointer_cast<Mob>(EntityIO::newByEnumType(factory,player->level ));
|
||||||
mob->moveTo(player->x+1, player->y, player->z+1, player->level->random->nextFloat() * 360, 0);
|
mob->moveTo(player->x+1, player->y, player->z+1, player->level->random->nextFloat() * 360, 0);
|
||||||
mob->setDespawnProtected(); // 4J added, default to being protected against despawning (has to be done after initial position is set)
|
mob->setDespawnProtected(); // 4J added, default to being protected against despawning (has to be done after initial position is set)
|
||||||
player->level->addEntity(mob);
|
player->level->addEntity(mob);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case eXuiServerAction_PauseServer:
|
case eXuiServerAction_PauseServer:
|
||||||
m_isServerPaused = ( (size_t) param == TRUE );
|
m_isServerPaused = ( (size_t) param == TRUE );
|
||||||
if( m_isServerPaused )
|
if( m_isServerPaused )
|
||||||
{
|
{
|
||||||
m_serverPausedEvent->Set();
|
m_serverPausedEvent->Set();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case eXuiServerAction_ToggleRain:
|
case eXuiServerAction_ToggleRain:
|
||||||
{
|
{
|
||||||
bool isRaining = levels[0]->getLevelData()->isRaining();
|
bool isRaining = levels[0]->getLevelData()->isRaining();
|
||||||
levels[0]->getLevelData()->setRaining(!isRaining);
|
levels[0]->getLevelData()->setRaining(!isRaining);
|
||||||
levels[0]->getLevelData()->setRainTime(levels[0]->random->nextInt(Level::TICKS_PER_DAY * 7) + Level::TICKS_PER_DAY / 2);
|
levels[0]->getLevelData()->setRainTime(levels[0]->random->nextInt(Level::TICKS_PER_DAY * 7) + Level::TICKS_PER_DAY / 2);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case eXuiServerAction_ToggleThunder:
|
case eXuiServerAction_ToggleThunder:
|
||||||
{
|
{
|
||||||
bool isThundering = levels[0]->getLevelData()->isThundering();
|
bool isThundering = levels[0]->getLevelData()->isThundering();
|
||||||
levels[0]->getLevelData()->setThundering(!isThundering);
|
levels[0]->getLevelData()->setThundering(!isThundering);
|
||||||
levels[0]->getLevelData()->setThunderTime(levels[0]->random->nextInt(Level::TICKS_PER_DAY * 7) + Level::TICKS_PER_DAY / 2);
|
levels[0]->getLevelData()->setThunderTime(levels[0]->random->nextInt(Level::TICKS_PER_DAY * 7) + Level::TICKS_PER_DAY / 2);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case eXuiServerAction_ServerSettingChanged_Gamertags:
|
case eXuiServerAction_ServerSettingChanged_Gamertags:
|
||||||
players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_OPTIONS, app.GetGameHostOption(eGameHostOption_Gamertags)));
|
players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_OPTIONS, app.GetGameHostOption(eGameHostOption_Gamertags)));
|
||||||
break;
|
break;
|
||||||
case eXuiServerAction_ServerSettingChanged_BedrockFog:
|
case eXuiServerAction_ServerSettingChanged_BedrockFog:
|
||||||
players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_IN_GAME_SETTINGS, app.GetGameHostOption(eGameHostOption_All)));
|
players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_IN_GAME_SETTINGS, app.GetGameHostOption(eGameHostOption_All)));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case eXuiServerAction_ServerSettingChanged_Difficulty:
|
case eXuiServerAction_ServerSettingChanged_Difficulty:
|
||||||
players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_DIFFICULTY, Minecraft::GetInstance()->options->difficulty));
|
players->broadcastAll(std::make_shared<ServerSettingsChangedPacket>(ServerSettingsChangedPacket::HOST_DIFFICULTY, Minecraft::GetInstance()->options->difficulty));
|
||||||
break;
|
break;
|
||||||
case eXuiServerAction_ExportSchematic:
|
case eXuiServerAction_ExportSchematic:
|
||||||
#ifndef _CONTENT_PACKAGE
|
#ifndef _CONTENT_PACKAGE
|
||||||
app.EnterSaveNotificationSection();
|
app.EnterSaveNotificationSection();
|
||||||
|
|
||||||
//players->broadcastAll( shared_ptr<UpdateProgressPacket>( new UpdateProgressPacket(20) ) );
|
//players->broadcastAll( shared_ptr<UpdateProgressPacket>( new UpdateProgressPacket(20) ) );
|
||||||
|
|
||||||
if( !s_bServerHalted )
|
if( !s_bServerHalted )
|
||||||
{
|
{
|
||||||
ConsoleSchematicFile::XboxSchematicInitParam *initData = static_cast<ConsoleSchematicFile::XboxSchematicInitParam *>(param);
|
ConsoleSchematicFile::XboxSchematicInitParam *initData = static_cast<ConsoleSchematicFile::XboxSchematicInitParam *>(param);
|
||||||
#ifdef _XBOX
|
#ifdef _XBOX
|
||||||
File targetFileDir(File::pathRoot + File::pathSeparator + L"Schematics");
|
File targetFileDir(File::pathRoot + File::pathSeparator + L"Schematics");
|
||||||
#else
|
#else
|
||||||
File targetFileDir(L"Schematics");
|
File targetFileDir(L"Schematics");
|
||||||
#endif
|
#endif
|
||||||
if(!targetFileDir.exists()) targetFileDir.mkdir();
|
if(!targetFileDir.exists()) targetFileDir.mkdir();
|
||||||
|
|
||||||
wchar_t filename[128];
|
wchar_t filename[128];
|
||||||
swprintf(filename,128,L"%ls%dx%dx%d.sch",initData->name,(initData->endX - initData->startX + 1), (initData->endY - initData->startY + 1), (initData->endZ - initData->startZ + 1));
|
swprintf(filename,128,L"%ls%dx%dx%d.sch",initData->name,(initData->endX - initData->startX + 1), (initData->endY - initData->startY + 1), (initData->endZ - initData->startZ + 1));
|
||||||
|
|
||||||
File dataFile = File( targetFileDir, wstring(filename) );
|
File dataFile = File( targetFileDir, wstring(filename) );
|
||||||
if(dataFile.exists()) dataFile._delete();
|
if(dataFile.exists()) dataFile._delete();
|
||||||
FileOutputStream fos = FileOutputStream(dataFile);
|
FileOutputStream fos = FileOutputStream(dataFile);
|
||||||
DataOutputStream dos = DataOutputStream(&fos);
|
DataOutputStream dos = DataOutputStream(&fos);
|
||||||
ConsoleSchematicFile::generateSchematicFile(&dos, levels[0], initData->startX, initData->startY, initData->startZ, initData->endX, initData->endY, initData->endZ, initData->bSaveMobs, initData->compressionType);
|
ConsoleSchematicFile::generateSchematicFile(&dos, levels[0], initData->startX, initData->startY, initData->startZ, initData->endX, initData->endY, initData->endZ, initData->bSaveMobs, initData->compressionType);
|
||||||
dos.close();
|
dos.close();
|
||||||
|
|
||||||
delete initData;
|
delete initData;
|
||||||
}
|
}
|
||||||
app.LeaveSaveNotificationSection();
|
app.LeaveSaveNotificationSection();
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case eXuiServerAction_SetCameraLocation:
|
case eXuiServerAction_SetCameraLocation:
|
||||||
#ifndef _CONTENT_PACKAGE
|
#ifndef _CONTENT_PACKAGE
|
||||||
{
|
{
|
||||||
DebugSetCameraPosition *pos = static_cast<DebugSetCameraPosition *>(param);
|
DebugSetCameraPosition *pos = static_cast<DebugSetCameraPosition *>(param);
|
||||||
|
|
||||||
app.DebugPrintf( "DEBUG: Player=%i\n", pos->player );
|
app.DebugPrintf( "DEBUG: Player=%i\n", pos->player );
|
||||||
app.DebugPrintf( "DEBUG: Teleporting to pos=(%f.2, %f.2, %f.2), looking at=(%f.2,%f.2)\n",
|
app.DebugPrintf( "DEBUG: Teleporting to pos=(%f.2, %f.2, %f.2), looking at=(%f.2,%f.2)\n",
|
||||||
pos->m_camX, pos->m_camY, pos->m_camZ,
|
pos->m_camX, pos->m_camY, pos->m_camZ,
|
||||||
pos->m_yRot, pos->m_elev
|
pos->m_yRot, pos->m_elev
|
||||||
);
|
);
|
||||||
|
|
||||||
shared_ptr<ServerPlayer> player = players->players.at(pos->player);
|
shared_ptr<ServerPlayer> player = players->players.at(pos->player);
|
||||||
player->debug_setPosition( pos->m_camX, pos->m_camY, pos->m_camZ,
|
player->debug_setPosition( pos->m_camX, pos->m_camY, pos->m_camZ,
|
||||||
pos->m_yRot, pos->m_elev );
|
pos->m_yRot, pos->m_elev );
|
||||||
|
|
||||||
// Doesn't work
|
// Doesn't work
|
||||||
//player->setYHeadRot(pos->m_yRot);
|
//player->setYHeadRot(pos->m_yRot);
|
||||||
//player->absMoveTo(pos->m_camX, pos->m_camY, pos->m_camZ, pos->m_yRot, pos->m_elev);
|
//player->absMoveTo(pos->m_camX, pos->m_camY, pos->m_camZ, pos->m_yRot, pos->m_elev);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
app.SetXuiServerAction(i,eXuiServerAction_Idle);
|
app.SetXuiServerAction(i,eXuiServerAction_Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
Sleep(1);
|
Sleep(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//else
|
//else
|
||||||
//{
|
//{
|
||||||
// while (running)
|
// while (running)
|
||||||
// {
|
// {
|
||||||
// handleConsoleInputs();
|
// handleConsoleInputs();
|
||||||
// Sleep(10);
|
// Sleep(10);
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
#if 0
|
#if 0
|
||||||
@@ -2065,9 +2080,9 @@ void MinecraftServer::run(int64_t seed, void *lpParameter)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// 4J Stu - Stop the server when the loops complete, as the finally would do
|
// 4J Stu - Stop the server when the loops complete, as the finally would do
|
||||||
stopServer(didInit);
|
stopServer(didInit);
|
||||||
stopped = true;
|
stopped = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MinecraftServer::broadcastStartSavingPacket()
|
void MinecraftServer::broadcastStartSavingPacket()
|
||||||
@@ -2375,6 +2390,9 @@ bool MinecraftServer::chunkPacketManagement_CanSendTo(INetworkPlayer *player)
|
|||||||
{
|
{
|
||||||
if( player == nullptr ) return false;
|
if( player == nullptr ) return false;
|
||||||
|
|
||||||
|
#ifdef MINECRAFT_SERVER_BUILD
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
int time = GetTickCount();
|
int time = GetTickCount();
|
||||||
DWORD currentPlayerCount = g_NetworkManager.GetPlayerCount();
|
DWORD currentPlayerCount = g_NetworkManager.GetPlayerCount();
|
||||||
if( currentPlayerCount == 0 ) return false;
|
if( currentPlayerCount == 0 ) return false;
|
||||||
@@ -2386,6 +2404,7 @@ bool MinecraftServer::chunkPacketManagement_CanSendTo(INetworkPlayer *player)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void MinecraftServer::chunkPacketManagement_DidSendTo(INetworkPlayer *player)
|
void MinecraftServer::chunkPacketManagement_DidSendTo(INetworkPlayer *player)
|
||||||
|
|||||||
@@ -146,7 +146,8 @@ void ServerPlayer::flagEntitiesToBeRemoved(unsigned int *flags, bool *removedFou
|
|||||||
if( ( *removedFound ) == false )
|
if( ( *removedFound ) == false )
|
||||||
{
|
{
|
||||||
*removedFound = true;
|
*removedFound = true;
|
||||||
memset(flags, 0, 2048/32);
|
// before this left 192 bytes uninitialized!!!!!
|
||||||
|
memset(flags, 0, (2048 / 32) * sizeof(unsigned int));
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int index : entitiesToRemove)
|
for(int index : entitiesToRemove)
|
||||||
@@ -376,6 +377,9 @@ void ServerPlayer::doChunkSendingTick(bool dontDelayChunks)
|
|||||||
// connection->done);
|
// connection->done);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
#ifdef MINECRAFT_SERVER_BUILD
|
||||||
|
if (dontDelayChunks || (canSendToPlayer && !connection->done))
|
||||||
|
#else
|
||||||
if( dontDelayChunks ||
|
if( dontDelayChunks ||
|
||||||
(canSendToPlayer &&
|
(canSendToPlayer &&
|
||||||
#ifdef _XBOX_ONE
|
#ifdef _XBOX_ONE
|
||||||
@@ -390,21 +394,22 @@ void ServerPlayer::doChunkSendingTick(bool dontDelayChunks)
|
|||||||
#endif
|
#endif
|
||||||
//(tickCount - lastBrupSendTickCount) > (connection->getNetworkPlayer()->GetCurrentRtt()>>4) &&
|
//(tickCount - lastBrupSendTickCount) > (connection->getNetworkPlayer()->GetCurrentRtt()>>4) &&
|
||||||
!connection->done) )
|
!connection->done) )
|
||||||
{
|
#endif
|
||||||
lastBrupSendTickCount = tickCount;
|
{
|
||||||
okToSend = true;
|
lastBrupSendTickCount = tickCount;
|
||||||
MinecraftServer::chunkPacketManagement_DidSendTo(connection->getNetworkPlayer());
|
okToSend = true;
|
||||||
|
MinecraftServer::chunkPacketManagement_DidSendTo(connection->getNetworkPlayer());
|
||||||
|
|
||||||
// static unordered_map<wstring,int64_t> mapLastTime;
|
// static unordered_map<wstring,int64_t> mapLastTime;
|
||||||
// int64_t thisTime = System::currentTimeMillis();
|
// int64_t thisTime = System::currentTimeMillis();
|
||||||
// int64_t lastTime = mapLastTime[connection->getNetworkPlayer()->GetUID().toString()];
|
// int64_t lastTime = mapLastTime[connection->getNetworkPlayer()->GetUID().toString()];
|
||||||
// app.DebugPrintf(" - OK to send (%d ms since last)\n", thisTime - lastTime);
|
// app.DebugPrintf(" - OK to send (%d ms since last)\n", thisTime - lastTime);
|
||||||
// mapLastTime[connection->getNetworkPlayer()->GetUID().toString()] = thisTime;
|
// mapLastTime[connection->getNetworkPlayer()->GetUID().toString()] = thisTime;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// app.DebugPrintf(" - <NOT OK>\n");
|
// app.DebugPrintf(" - <NOT OK>\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (okToSend)
|
if (okToSend)
|
||||||
|
|||||||
@@ -96,9 +96,20 @@ int Entity::getSmallId()
|
|||||||
puiUsedFlags++;
|
puiUsedFlags++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef MINECRAFT_SERVER_BUILD
|
||||||
|
// in mc server dedi, a server with 8+ playerrs can cause this to go wack
|
||||||
|
int fallbackId = Entity::entityCounter++;
|
||||||
|
|
||||||
|
if (entityCounter == 0x7ffffff)
|
||||||
|
{
|
||||||
|
entityCounter = 2048;
|
||||||
|
}
|
||||||
|
return fallbackId;
|
||||||
|
#else
|
||||||
app.DebugPrintf("Out of small entity Ids... possible leak?\n");
|
app.DebugPrintf("Out of small entity Ids... possible leak?\n");
|
||||||
__debugbreak();
|
__debugbreak();
|
||||||
return -1;
|
return -1;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void Entity::countFlagsForPIX()
|
void Entity::countFlagsForPIX()
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ using namespace std;
|
|||||||
|
|
||||||
// 4J Stu - This value should be big enough that we don't get any crashes causes by memory overwrites,
|
// 4J Stu - This value should be big enough that we don't get any crashes causes by memory overwrites,
|
||||||
// however it does seem way too large for what is actually needed. Needs further investigation
|
// however it does seem way too large for what is actually needed. Needs further investigation
|
||||||
|
#ifdef MINECRAFT_SERVER_BUILD
|
||||||
|
// fixes a crash when 8+ players are present
|
||||||
|
#define LEVEL_CHUNKS_TO_UPDATE_MAX (32*32*8)
|
||||||
|
#else
|
||||||
#define LEVEL_CHUNKS_TO_UPDATE_MAX (19*19*8)
|
#define LEVEL_CHUNKS_TO_UPDATE_MAX (19*19*8)
|
||||||
|
#endif
|
||||||
|
|
||||||
class Vec3;
|
class Vec3;
|
||||||
class ChunkSource;
|
class ChunkSource;
|
||||||
|
|||||||
@@ -30,6 +30,65 @@ ensure_persist_file() {
|
|||||||
ln -sfn "${persist_path}" "${runtime_path}"
|
ln -sfn "${persist_path}" "${runtime_path}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wait_for_xvfb_ready() {
|
||||||
|
local display="$1"
|
||||||
|
local xvfb_pid="$2"
|
||||||
|
local wait_seconds="${XVFB_WAIT_SECONDS:-10}"
|
||||||
|
local wait_ticks=$((wait_seconds * 10))
|
||||||
|
local display_number="${display#:}"
|
||||||
|
display_number="${display_number%%.*}"
|
||||||
|
|
||||||
|
if [ -z "${display_number}" ] || ! [[ "${display_number}" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "[error] Invalid DISPLAY format for Xvfb wait: ${display}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local socket_path="/tmp/.X11-unix/X${display_number}"
|
||||||
|
local elapsed=0
|
||||||
|
|
||||||
|
while [ "${elapsed}" -lt "${wait_ticks}" ]; do
|
||||||
|
if ! kill -0 "${xvfb_pid}" 2>/dev/null; then
|
||||||
|
echo "[error] Xvfb exited before the display became ready." >&2
|
||||||
|
if [ -f /tmp/xvfb.log ]; then
|
||||||
|
echo "[error] ---- /tmp/xvfb.log ----" >&2
|
||||||
|
tail -n 200 /tmp/xvfb.log >&2 || true
|
||||||
|
echo "[error] ----------------------" >&2
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -S "${socket_path}" ]; then
|
||||||
|
# Keep a short extra delay so Wine does not race display handshake.
|
||||||
|
sleep 0.2
|
||||||
|
if kill -0 "${xvfb_pid}" 2>/dev/null && [ -S "${socket_path}" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# One more liveness check after the ready probe branch.
|
||||||
|
if ! kill -0 "${xvfb_pid}" 2>/dev/null; then
|
||||||
|
echo "[error] Xvfb exited during display readiness probe." >&2
|
||||||
|
if [ -f /tmp/xvfb.log ]; then
|
||||||
|
echo "[error] ---- /tmp/xvfb.log ----" >&2
|
||||||
|
tail -n 200 /tmp/xvfb.log >&2 || true
|
||||||
|
echo "[error] ----------------------" >&2
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 0.1
|
||||||
|
elapsed=$((elapsed + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "[error] Timed out waiting for Xvfb display ${display}." >&2
|
||||||
|
if [ -f /tmp/xvfb.log ]; then
|
||||||
|
echo "[error] ---- /tmp/xvfb.log ----" >&2
|
||||||
|
tail -n 200 /tmp/xvfb.log >&2 || true
|
||||||
|
echo "[error] ----------------------" >&2
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
if [ ! -d "$SERVER_DIR" ]; then
|
if [ ! -d "$SERVER_DIR" ]; then
|
||||||
echo "[error] Server directory not found: $SERVER_DIR" >&2
|
echo "[error] Server directory not found: $SERVER_DIR" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -78,8 +137,25 @@ fi
|
|||||||
if [ -z "${DISPLAY:-}" ]; then
|
if [ -z "${DISPLAY:-}" ]; then
|
||||||
export DISPLAY="${XVFB_DISPLAY:-:99}"
|
export DISPLAY="${XVFB_DISPLAY:-:99}"
|
||||||
XVFB_SCREEN="${XVFB_SCREEN:-64x64x16}"
|
XVFB_SCREEN="${XVFB_SCREEN:-64x64x16}"
|
||||||
|
DISPLAY_NUMBER="${DISPLAY#:}"
|
||||||
|
DISPLAY_NUMBER="${DISPLAY_NUMBER%%.*}"
|
||||||
|
if [ -z "${DISPLAY_NUMBER}" ] || ! [[ "${DISPLAY_NUMBER}" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "[error] Invalid XVFB_DISPLAY format: ${DISPLAY}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
XVFB_SOCKET="/tmp/.X11-unix/X${DISPLAY_NUMBER}"
|
||||||
|
XVFB_LOCK="/tmp/.X${DISPLAY_NUMBER}-lock"
|
||||||
|
# The check is performed assuming the same container will be restarted.
|
||||||
|
if [ -S "${XVFB_SOCKET}" ] || [ -e "${XVFB_LOCK}" ]; then
|
||||||
|
echo "[warn] Removing stale Xvfb state for ${DISPLAY} before startup." >&2
|
||||||
|
rm -f "${XVFB_SOCKET}" "${XVFB_LOCK}"
|
||||||
|
fi
|
||||||
Xvfb "${DISPLAY}" -nolisten tcp -screen 0 "${XVFB_SCREEN}" >/tmp/xvfb.log 2>&1 &
|
Xvfb "${DISPLAY}" -nolisten tcp -screen 0 "${XVFB_SCREEN}" >/tmp/xvfb.log 2>&1 &
|
||||||
sleep 0.2
|
XVFB_PID=$!
|
||||||
|
wait_for_xvfb_ready "${DISPLAY}" "${XVFB_PID}"
|
||||||
|
echo "[info] Xvfb ready on ${DISPLAY} (pid=${XVFB_PID}, screen=${XVFB_SCREEN})"
|
||||||
|
else
|
||||||
|
echo "[info] Using existing DISPLAY=${DISPLAY}; skipping Xvfb startup"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
args=(
|
args=(
|
||||||
|
|||||||
Reference in New Issue
Block a user