forked from aya/aya
Initial commit
This commit is contained in:
304
client/studio/src/RobloxApplicationManager.cpp
Normal file
304
client/studio/src/RobloxApplicationManager.cpp
Normal file
@@ -0,0 +1,304 @@
|
||||
|
||||
|
||||
|
||||
#include "RobloxApplicationManager.hpp"
|
||||
|
||||
// Qt Headers
|
||||
#include <QStringList>
|
||||
#include <QCoreApplication>
|
||||
#include <QProcess>
|
||||
#include <QLocalSocket>
|
||||
#include <QLocalServer>
|
||||
|
||||
// Roblox Headers
|
||||
#include "RobloxCookieJar.hpp"
|
||||
#include "RobloxMainWindow.hpp"
|
||||
#include "RobloxNetworkAccessManager.hpp"
|
||||
|
||||
#include "UpdateUIManager.hpp"
|
||||
#include "QtUtilities.hpp"
|
||||
#include "StudioDeviceEmulator.hpp"
|
||||
#include "StudioUtilities.hpp"
|
||||
|
||||
const char* kChildProcessServerName = "rbxDeleteChildProcessesServer_%1";
|
||||
const char* kDeleteChildProcessesMsg = "rbxDeleteChildProcesses";
|
||||
const char* kChildProcessesDeletedMsg = "rbxChildProcessesDeleted";
|
||||
|
||||
RobloxApplicationManager::RobloxApplicationManager()
|
||||
: childProcessHandler(NULL)
|
||||
{
|
||||
sharedMemory.setKey("QT_ROBLOX_STUDIO");
|
||||
}
|
||||
|
||||
RobloxApplicationManager& RobloxApplicationManager::instance()
|
||||
{
|
||||
static RobloxApplicationManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool RobloxApplicationManager::createNewStudioInstance(
|
||||
const QString& script, const QString& fileLocation, bool isTestMode, bool isAvatarMode, NewInstanceMode mode)
|
||||
{
|
||||
// Save the current layout before launching
|
||||
UpdateUIManager::Instance().getMainWindow().saveApplicationStates();
|
||||
|
||||
// Persist our cookies (to make sure we have the most recent ones in the new app)
|
||||
RobloxCookieJar* cookieJar = dynamic_cast<RobloxCookieJar*>(RobloxNetworkAccessManager::Instance().cookieJar());
|
||||
if (cookieJar)
|
||||
cookieJar->saveCookiesToDisk();
|
||||
|
||||
QStringList args;
|
||||
args << IDEArgument;
|
||||
composeArgList(args, script, fileLocation, isTestMode, isAvatarMode);
|
||||
|
||||
QString robloxApp = QCoreApplication::applicationFilePath();
|
||||
|
||||
if (mode == NewInstanceMode_Detached)
|
||||
return QProcess::startDetached(robloxApp, args);
|
||||
|
||||
if (!childProcessHandler)
|
||||
childProcessHandler = new ChildProcessHandler(&UpdateUIManager::Instance().getMainWindow());
|
||||
return childProcessHandler->startChildProcess(robloxApp, args, mode);
|
||||
}
|
||||
|
||||
int RobloxApplicationManager::getApplicationCount()
|
||||
{
|
||||
SharedData data;
|
||||
if (!sharedMemory.lock())
|
||||
return 0;
|
||||
char* sharedData = (char*)sharedMemory.data();
|
||||
|
||||
if (!sharedData) // Odd scenario, should this ever happen?
|
||||
{
|
||||
sharedMemory.unlock();
|
||||
return 0;
|
||||
}
|
||||
memcpy(&data, sharedData, sizeof(SharedData));
|
||||
|
||||
sharedMemory.unlock();
|
||||
|
||||
return data.numStudioApplications;
|
||||
}
|
||||
|
||||
void RobloxApplicationManager::registerApplication()
|
||||
{
|
||||
sharedMemory.attach();
|
||||
sharedMemory.create(sizeof(SharedData)); // Create it if it doesn't exist
|
||||
modifyApplicationCount(1); // Increment our count
|
||||
}
|
||||
|
||||
void RobloxApplicationManager::unregisterApplication()
|
||||
{
|
||||
modifyApplicationCount(-1); // Decrement our count
|
||||
sharedMemory.detach();
|
||||
}
|
||||
|
||||
void RobloxApplicationManager::modifyApplicationCount(int delta)
|
||||
{
|
||||
if (!sharedMemory.lock())
|
||||
return;
|
||||
|
||||
SharedData data;
|
||||
|
||||
char* sharedData = (char*)sharedMemory.data();
|
||||
|
||||
if (sharedData)
|
||||
{
|
||||
memcpy(&data, sharedData, sizeof(SharedData));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Should never be here?
|
||||
data.numStudioApplications = 0;
|
||||
}
|
||||
|
||||
data.numStudioApplications += delta;
|
||||
|
||||
memcpy(sharedData, &data, sizeof(SharedData));
|
||||
|
||||
sharedMemory.unlock();
|
||||
}
|
||||
|
||||
void RobloxApplicationManager::composeArgList(
|
||||
QStringList& argsContainer, const QString& script, const QString& fileLocation, bool isTestMode, bool isAvatarMode)
|
||||
{
|
||||
if (!script.isEmpty())
|
||||
argsContainer << ScriptArgument << script;
|
||||
if (!fileLocation.isEmpty())
|
||||
argsContainer << FileLocationArgument << fileLocation;
|
||||
if (isTestMode)
|
||||
argsContainer << TestModeArgument;
|
||||
if (isAvatarMode)
|
||||
argsContainer << AvatarModeArgument;
|
||||
|
||||
if (StudioDeviceEmulator::Instance().isEmulatingTouch())
|
||||
argsContainer << StudioUtilities::EmulateTouchArgument;
|
||||
|
||||
int studioWidth = StudioDeviceEmulator::Instance().getCurrentDeviceWidth();
|
||||
int studioHeight = StudioDeviceEmulator::Instance().getCurrentDeviceHeight();
|
||||
|
||||
if (studioWidth)
|
||||
argsContainer << StudioUtilities::StudioWidthArgument << QString("%1").arg(studioWidth);
|
||||
|
||||
if (studioHeight)
|
||||
argsContainer << StudioUtilities::StudioHeightArgument << QString("%1").arg(studioHeight);
|
||||
}
|
||||
|
||||
bool RobloxApplicationManager::hasChildProcesses()
|
||||
{
|
||||
if (childProcessHandler)
|
||||
return childProcessHandler->hasChildProcesses();
|
||||
return false;
|
||||
}
|
||||
|
||||
void RobloxApplicationManager::cleanupChildProcesses(int timeout)
|
||||
{
|
||||
if (childProcessHandler)
|
||||
childProcessHandler->cleanupChildProcesses(timeout);
|
||||
}
|
||||
|
||||
void RobloxApplicationManager::startLocalServer()
|
||||
{
|
||||
if (!childProcessHandler)
|
||||
childProcessHandler = new ChildProcessHandler(&UpdateUIManager::Instance().getMainWindow());
|
||||
childProcessHandler->startLocalServer();
|
||||
}
|
||||
|
||||
bool RobloxApplicationManager::hasLocalServer()
|
||||
{
|
||||
if (childProcessHandler)
|
||||
return childProcessHandler->hasLocalServer();
|
||||
return false;
|
||||
}
|
||||
|
||||
ChildProcessHandler::ChildProcessHandler(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_pLocalServer(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
bool ChildProcessHandler::startChildProcess(const QString& robloxApp, const QStringList& args, NewInstanceMode mode)
|
||||
{
|
||||
QProcess* newProcess = new QProcess();
|
||||
newProcess->start(robloxApp, args);
|
||||
|
||||
if (!newProcess->waitForStarted())
|
||||
return false;
|
||||
|
||||
if (mode == NewInstanceMode_Detached)
|
||||
return true;
|
||||
|
||||
// child process can be managed if it's Server or Player
|
||||
(mode == NewInstanceMode_Server) ? serverProcesses.append(newProcess) : playerProcesses.append(newProcess);
|
||||
connect(newProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onProcessFinished()));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ChildProcessHandler::startLocalServer()
|
||||
{
|
||||
// if we already have a local server, do not do anything
|
||||
if (m_pLocalServer)
|
||||
return;
|
||||
|
||||
m_pLocalServer = new QLocalServer(this);
|
||||
connect(m_pLocalServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
|
||||
|
||||
int processId = 0;
|
||||
#ifdef Q_WS_WIN32
|
||||
processId = ::GetCurrentProcessId();
|
||||
#else
|
||||
processId = getpid();
|
||||
#endif
|
||||
|
||||
// set name for the local server (attach process id to make it unique)
|
||||
m_pLocalServer->listen(QString(kChildProcessServerName).arg(processId));
|
||||
}
|
||||
|
||||
bool ChildProcessHandler::hasLocalServer()
|
||||
{
|
||||
return m_pLocalServer != NULL;
|
||||
}
|
||||
|
||||
void ChildProcessHandler::cleanupChildProcesses(int timeout)
|
||||
{
|
||||
// first cleanup players launched from this instance of studio
|
||||
QProcess* childProcess = NULL;
|
||||
for (int ii = 0; ii < playerProcesses.size(); ++ii)
|
||||
{
|
||||
childProcess = playerProcesses.at(ii);
|
||||
if (childProcess && childProcess->state() != QProcess::NotRunning)
|
||||
{
|
||||
childProcess->kill();
|
||||
}
|
||||
}
|
||||
playerProcesses.clear();
|
||||
|
||||
// now time to remove server processes
|
||||
for (int ii = 0; ii < serverProcesses.size(); ++ii)
|
||||
{
|
||||
childProcess = serverProcesses.at(ii);
|
||||
if (childProcess && childProcess->state() != QProcess::NotRunning)
|
||||
{
|
||||
int processId = QtUtilities::toInt(childProcess->processId());
|
||||
if (!processId)
|
||||
continue;
|
||||
|
||||
// since server launches player (in Ribbon mode), we need to make sure those launched players also gets cleaned up
|
||||
QLocalSocket localSocket;
|
||||
localSocket.connectToServer(QString(kChildProcessServerName).arg(processId));
|
||||
if (localSocket.waitForConnected(timeout))
|
||||
{
|
||||
QString message(kDeleteChildProcessesMsg);
|
||||
localSocket.write(message.toUtf8());
|
||||
localSocket.waitForBytesWritten(timeout);
|
||||
|
||||
localSocket.waitForReadyRead(timeout);
|
||||
}
|
||||
|
||||
// now we are done with players cleanup, all set to kill the server
|
||||
if (childProcess && childProcess->state() != QProcess::NotRunning)
|
||||
childProcess->kill();
|
||||
}
|
||||
}
|
||||
serverProcesses.clear();
|
||||
}
|
||||
|
||||
bool ChildProcessHandler::hasChildProcesses()
|
||||
{
|
||||
return !serverProcesses.isEmpty() || !playerProcesses.isEmpty();
|
||||
}
|
||||
|
||||
void ChildProcessHandler::onProcessFinished()
|
||||
{
|
||||
QProcess* pProcess = qobject_cast<QProcess*>(sender());
|
||||
if (!pProcess)
|
||||
return;
|
||||
|
||||
// check if we can remove process from list
|
||||
bool removed = serverProcesses.removeOne(pProcess);
|
||||
if (!removed)
|
||||
removed = playerProcesses.removeOne(pProcess);
|
||||
|
||||
// if we do not have any child processes pending update toolbars
|
||||
if (removed && serverProcesses.isEmpty() && playerProcesses.isEmpty())
|
||||
UpdateUIManager::Instance().updateToolBars();
|
||||
}
|
||||
|
||||
void ChildProcessHandler::onNewConnection()
|
||||
{
|
||||
if (!m_pLocalServer)
|
||||
return;
|
||||
|
||||
QLocalSocket* localSocket = m_pLocalServer->nextPendingConnection();
|
||||
if (localSocket && localSocket->waitForReadyRead(3000))
|
||||
{
|
||||
QString receivedMessage = QString::fromUtf8(localSocket->readAll().constData());
|
||||
if (!receivedMessage.isEmpty() && (receivedMessage == kDeleteChildProcessesMsg))
|
||||
{
|
||||
// kill all child process
|
||||
cleanupChildProcesses();
|
||||
// update socket about the completion
|
||||
localSocket->write(kChildProcessesDeletedMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user