You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1154 lines
30 KiB
C++
1154 lines
30 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
// Original file Copyright Crytek GMBH or its affiliates, used under license.
|
|
|
|
// Description : CWindowsConsole member definitions
|
|
|
|
|
|
#include "CrySystem_precompiled.h"
|
|
#include "System.h"
|
|
#include "WindowsConsole.h"
|
|
|
|
#ifdef USE_WINDOWSCONSOLE
|
|
|
|
#define WINDOWS_CONSOLE_WIDTH 128
|
|
#define WINDOWS_CONSOLE_HEIGHT 50
|
|
#define WINDOWS_CONSOLE_LOG_BUFFER_LINES 1024
|
|
#define WINDOWS_CONSOLE_LOG_SCROLL_LINES 10
|
|
#define WINDOWS_CONSOLE_TAB_SIZE 4
|
|
#define WINDOWS_CONSOLE_CRYENGINE_BLACK 0x0
|
|
#define WINDOWS_CONSOLE_CRYENGINE_WHITE 0x1
|
|
#define WINDOWS_CONSOLE_CRYENGINE_BLUE 0x2
|
|
#define WINDOWS_CONSOLE_CRYENGINE_GREEN 0x3
|
|
#define WINDOWS_CONSOLE_CRYENGINE_RED 0x4
|
|
#define WINDOWS_CONSOLE_CRYENGINE_CYAN 0x5
|
|
#define WINDOWS_CONSOLE_CRYENGINE_YELLOW 0x6
|
|
#define WINDOWS_CONSOLE_CRYENGINE_MAGENTA 0x7
|
|
#define WINDOWS_CONSOLE_CRYENGINE_ORANGE 0x8
|
|
#define WINDOWS_CONSOLE_CRYENGINE_GREY 0x9
|
|
#define WINDOWS_CONSOLE_NATIVE_BLACK 0x0
|
|
#define WINDOWS_CONSOLE_NATIVE_BROWN 0x6
|
|
#define WINDOWS_CONSOLE_NATIVE_LIGHTGREY 0x7
|
|
#define WINDOWS_CONSOLE_NATIVE_LIGHTBLUE 0x9
|
|
#define WINDOWS_CONSOLE_NATIVE_LIGHTGREEN 0xA
|
|
#define WINDOWS_CONSOLE_NATIVE_LIGHTCYAN 0xB
|
|
#define WINDOWS_CONSOLE_NATIVE_LIGHTRED 0xC
|
|
#define WINDOWS_CONSOLE_NATIVE_LIGHTMAGENTA 0xD
|
|
#define WINDOWS_CONSOLE_NATIVE_YELLOW 0xE
|
|
#define WINDOWS_CONSOLE_NATIVE_WHITE 0xF
|
|
#define WINDOWS_CONSOLE_COLOR_MASK 0xF
|
|
#define WINDOWS_CONSOLE_BGCOLOR_SHIFT 4
|
|
|
|
const uint8 CWindowsConsole::s_colorTable[ WINDOWS_CONSOLE_NUM_CRYENGINE_COLORS ] =
|
|
{
|
|
WINDOWS_CONSOLE_NATIVE_BLACK,
|
|
WINDOWS_CONSOLE_NATIVE_WHITE,
|
|
WINDOWS_CONSOLE_NATIVE_LIGHTBLUE,
|
|
WINDOWS_CONSOLE_NATIVE_LIGHTGREEN,
|
|
WINDOWS_CONSOLE_NATIVE_LIGHTRED,
|
|
WINDOWS_CONSOLE_NATIVE_LIGHTCYAN,
|
|
WINDOWS_CONSOLE_NATIVE_YELLOW,
|
|
WINDOWS_CONSOLE_NATIVE_LIGHTMAGENTA,
|
|
WINDOWS_CONSOLE_NATIVE_BROWN,
|
|
WINDOWS_CONSOLE_NATIVE_LIGHTGREY
|
|
};
|
|
|
|
CWindowsConsole::CWindowsConsole()
|
|
: m_lock()
|
|
, m_consoleScreenBufferSize()
|
|
, m_consoleWindow()
|
|
, m_inputBufferHandle(INVALID_HANDLE_VALUE)
|
|
, m_screenBufferHandle(INVALID_HANDLE_VALUE)
|
|
, m_logBuffer(0, 0, WINDOWS_CONSOLE_WIDTH, WINDOWS_CONSOLE_HEIGHT - 2, WINDOWS_CONSOLE_LOG_BUFFER_LINES, L' ', WINDOWS_CONSOLE_CRYENGINE_GREY, WINDOWS_CONSOLE_CRYENGINE_BLACK)
|
|
, m_fullScreenBuffer(0, 0, WINDOWS_CONSOLE_WIDTH, WINDOWS_CONSOLE_HEIGHT - 2, WINDOWS_CONSOLE_HEIGHT - 2, L' ', WINDOWS_CONSOLE_CRYENGINE_GREY, WINDOWS_CONSOLE_CRYENGINE_BLACK)
|
|
, m_statusBuffer(0, WINDOWS_CONSOLE_HEIGHT - 2, WINDOWS_CONSOLE_WIDTH, 1, 1, L' ', WINDOWS_CONSOLE_CRYENGINE_BLACK, WINDOWS_CONSOLE_CRYENGINE_GREY)
|
|
, m_commandBuffer(0, WINDOWS_CONSOLE_HEIGHT - 1, WINDOWS_CONSOLE_WIDTH, 1, 1, L' ', WINDOWS_CONSOLE_CRYENGINE_WHITE, WINDOWS_CONSOLE_CRYENGINE_BLACK)
|
|
, m_dirtyCellBuffers(0)
|
|
, m_commandQueue()
|
|
, m_commandPrompt("] ")
|
|
, m_commandPromptLength(m_commandPrompt.length())
|
|
, m_command()
|
|
, m_commandCursor(0)
|
|
, m_logLine()
|
|
, m_progressString()
|
|
, m_header()
|
|
, m_updStats()
|
|
, m_pInputThread(NULL)
|
|
, m_pSystem(NULL)
|
|
, m_pConsole(NULL)
|
|
, m_pTimer(NULL)
|
|
, m_pCVarSvMap(NULL)
|
|
, m_pCVarSvMission(NULL)
|
|
, m_pCVarSvGameRules(NULL)
|
|
, m_lastStatusUpdate()
|
|
, m_lastUpdateTime()
|
|
, m_initialized(false)
|
|
, m_OnUpdateCalled(false)
|
|
, m_requireDedicatedServer(false)
|
|
{
|
|
}
|
|
|
|
CWindowsConsole::~CWindowsConsole()
|
|
{
|
|
CleanUp();
|
|
}
|
|
|
|
Vec2_tpl< int > CWindowsConsole::BeginDraw()
|
|
{
|
|
m_newCmds.resize(0);
|
|
return Vec2_tpl< int >(WINDOWS_CONSOLE_WIDTH, WINDOWS_CONSOLE_HEIGHT - 2);
|
|
}
|
|
|
|
void CWindowsConsole::PutText(int x, int y, const char* pMsg)
|
|
{
|
|
SConDrawCmd cmd;
|
|
|
|
cmd.x = x;
|
|
cmd.y = y;
|
|
cry_strcpy(cmd.text, pMsg);
|
|
m_newCmds.push_back(cmd);
|
|
}
|
|
|
|
void CWindowsConsole::EndDraw()
|
|
{
|
|
Lock();
|
|
m_drawCmds.swap(m_newCmds);
|
|
Unlock();
|
|
}
|
|
|
|
void CWindowsConsole::SetTitle(const char* title)
|
|
{
|
|
m_title = title;
|
|
|
|
if (m_title.empty())
|
|
{
|
|
SetConsoleTitle(m_header.c_str());
|
|
}
|
|
else
|
|
{
|
|
stack_string fullHeader = m_title + " - " + m_header;
|
|
SetConsoleTitle(fullHeader);
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::Print(const char* pInszText)
|
|
{
|
|
Lock();
|
|
|
|
bool isContinue = true;
|
|
const char* pInszTextPtr = pInszText;
|
|
const char* pLogLinePtr = m_logLine.c_str();
|
|
|
|
while (*pLogLinePtr && isContinue)
|
|
{
|
|
if (*pInszTextPtr != *pLogLinePtr)
|
|
{
|
|
isContinue = false;
|
|
}
|
|
|
|
++pInszTextPtr;
|
|
++pLogLinePtr;
|
|
}
|
|
|
|
// Do not treat lines as equal if the new line starts the same as the previous line
|
|
isContinue = isContinue && (*pInszTextPtr == 0 || m_logLine.empty());
|
|
|
|
if (!isContinue)
|
|
{
|
|
pInszTextPtr = pInszText;
|
|
m_logBuffer.NewLine();
|
|
m_logLine.clear();
|
|
}
|
|
|
|
m_logLine.append(pInszTextPtr);
|
|
m_logBuffer.Print(pInszTextPtr);
|
|
m_dirtyCellBuffers |= eCBB_Log;
|
|
|
|
Unlock();
|
|
}
|
|
|
|
bool CWindowsConsole::OnError([[maybe_unused]] const char* szErrorString)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool CWindowsConsole::OnSaveDocument()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool CWindowsConsole::OnBackupDocument()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void CWindowsConsole::OnProcessSwitch()
|
|
{
|
|
}
|
|
|
|
void CWindowsConsole::OnInitProgress(const char* sProgressMsg)
|
|
{
|
|
if (m_initialized)
|
|
{
|
|
Lock();
|
|
m_progressString = sProgressMsg;
|
|
DrawStatus();
|
|
Unlock();
|
|
}
|
|
}
|
|
|
|
// the CtrlHandler will be called from a separate thread that only handles
|
|
// Ctrl messages. When the CLOSE event is sent, this function can just wait
|
|
// forever, as FreeConsole() will kill the thread. If this function returns
|
|
// immediately, windows will call TerminateProcess() and nothing will be cleaned up.
|
|
static BOOL WINAPI CtrlHandler(DWORD ctrlEvent)
|
|
{
|
|
switch (ctrlEvent)
|
|
{
|
|
case CTRL_C_EVENT:
|
|
case CTRL_BREAK_EVENT:
|
|
case CTRL_LOGOFF_EVENT:
|
|
case CTRL_SHUTDOWN_EVENT:
|
|
return TRUE;
|
|
case CTRL_CLOSE_EVENT:
|
|
if ( (gEnv != nullptr) && (gEnv->pSystem != nullptr) && (gEnv->pSystem->GetIConsole() != nullptr) )
|
|
{
|
|
gEnv->pSystem->GetIConsole()->ExecuteString("quit", true, true);
|
|
Sleep(INFINITE);
|
|
return TRUE;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void CWindowsConsole::OnInit(ISystem* pSystem)
|
|
{
|
|
if (m_requireDedicatedServer && !gEnv->IsDedicated())
|
|
{
|
|
return;
|
|
}
|
|
|
|
Lock();
|
|
|
|
if (!m_initialized)
|
|
{
|
|
assert(m_pSystem == NULL);
|
|
assert(m_pConsole == NULL);
|
|
|
|
m_pSystem = pSystem;
|
|
m_pConsole = pSystem->GetIConsole();
|
|
|
|
AllocConsole();
|
|
m_inputBufferHandle = GetStdHandle(STD_INPUT_HANDLE);
|
|
m_screenBufferHandle = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
SetConsoleMode(m_inputBufferHandle, ENABLE_WINDOW_INPUT);
|
|
m_consoleScreenBufferSize.X = WINDOWS_CONSOLE_WIDTH;
|
|
m_consoleScreenBufferSize.Y = WINDOWS_CONSOLE_HEIGHT;
|
|
m_consoleWindow.Left = 0;
|
|
m_consoleWindow.Top = 0;
|
|
m_consoleWindow.Right = WINDOWS_CONSOLE_WIDTH - 1;
|
|
m_consoleWindow.Bottom = WINDOWS_CONSOLE_HEIGHT - 1;
|
|
SetConsoleScreenBufferSize(m_screenBufferHandle, m_consoleScreenBufferSize);
|
|
SetConsoleWindowInfo(m_screenBufferHandle, TRUE, &m_consoleWindow);
|
|
SetConsoleTitle(m_header.c_str());
|
|
|
|
if (m_pConsole)
|
|
{
|
|
m_pConsole->AddOutputPrintSink(this);
|
|
}
|
|
|
|
DrawCommand();
|
|
|
|
m_pInputThread = new CWindowsConsoleInputThread(*this);
|
|
m_pInputThread->Start();
|
|
|
|
#if !defined(NDEBUG)
|
|
BOOL handlerInstalled =
|
|
#endif
|
|
SetConsoleCtrlHandler(CtrlHandler, TRUE);
|
|
CRY_ASSERT(handlerInstalled);
|
|
|
|
m_initialized = true;
|
|
}
|
|
|
|
Unlock();
|
|
}
|
|
|
|
void CWindowsConsole::OnShutdown()
|
|
{
|
|
CleanUp();
|
|
}
|
|
|
|
void CWindowsConsole::OnUpdate()
|
|
{
|
|
if (m_initialized)
|
|
{
|
|
Lock();
|
|
|
|
bool updateStatus = false;
|
|
|
|
if (!m_OnUpdateCalled)
|
|
{
|
|
assert(m_pCVarSvMap == NULL);
|
|
assert(m_pCVarSvGameRules == NULL);
|
|
assert(m_pTimer == NULL);
|
|
|
|
m_pCVarSvMap = m_pConsole->GetCVar("sv_map");
|
|
m_pCVarSvGameRules = m_pConsole->GetCVar("sv_gamerules");
|
|
m_pTimer = m_pSystem->GetITimer();
|
|
m_OnUpdateCalled = true;
|
|
|
|
assert(m_pCVarSvMission == NULL);
|
|
m_pCVarSvMission = m_pConsole->GetCVar("sv_mission");
|
|
}
|
|
|
|
if (!m_progressString.empty())
|
|
{
|
|
m_progressString.clear();
|
|
updateStatus = true;
|
|
}
|
|
|
|
CTimeValue now = m_pTimer->GetAsyncTime();
|
|
|
|
if ((now - m_lastStatusUpdate).GetSeconds() > 0.1F)
|
|
{
|
|
updateStatus = true;
|
|
}
|
|
|
|
m_lastUpdateTime = now;
|
|
|
|
m_pSystem->GetUpdateStats(m_updStats);
|
|
|
|
if (updateStatus)
|
|
{
|
|
DrawStatus();
|
|
m_lastStatusUpdate = now;
|
|
}
|
|
|
|
while (m_commandQueue.size())
|
|
{
|
|
const string& command = m_commandQueue[0];
|
|
Unlock();
|
|
// 'm_pConsole' will be set to NULL when executing 'quit' command
|
|
// Cache pointer in local variable to prevent crash when adding the last command to the history
|
|
IConsole* pConsole = m_pConsole;
|
|
pConsole->ExecuteString(command.c_str());
|
|
pConsole->AddCommandToHistory(command.c_str());
|
|
Lock();
|
|
m_commandQueue.pop_front();
|
|
}
|
|
|
|
if (!m_drawCmds.empty())
|
|
{
|
|
DrawFull();
|
|
}
|
|
|
|
Repaint();
|
|
|
|
Unlock();
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::OnConsoleInputEvent(INPUT_RECORD inputRecord)
|
|
{
|
|
switch (inputRecord.EventType)
|
|
{
|
|
case KEY_EVENT:
|
|
OnKey(inputRecord.Event.KeyEvent);
|
|
break;
|
|
case WINDOW_BUFFER_SIZE_EVENT:
|
|
OnResize(inputRecord.Event.WindowBufferSizeEvent.dwSize);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::OnKey(const KEY_EVENT_RECORD& event)
|
|
{
|
|
if (event.bKeyDown)
|
|
{
|
|
for (uint32 i = 0; i < event.wRepeatCount; ++i)
|
|
{
|
|
switch (event.wVirtualKeyCode)
|
|
{
|
|
case VK_BACK:
|
|
OnBackspace();
|
|
break;
|
|
case VK_TAB:
|
|
OnTab();
|
|
break;
|
|
case VK_RETURN:
|
|
OnReturn();
|
|
break;
|
|
case VK_PRIOR:
|
|
OnPgUp();
|
|
break;
|
|
case VK_NEXT:
|
|
OnPgDn();
|
|
break;
|
|
case VK_LEFT:
|
|
OnLeft();
|
|
break;
|
|
case VK_UP:
|
|
OnUp();
|
|
break;
|
|
case VK_RIGHT:
|
|
OnRight();
|
|
break;
|
|
case VK_DOWN:
|
|
OnDown();
|
|
break;
|
|
case VK_DELETE:
|
|
OnDelete();
|
|
break;
|
|
default:
|
|
OnChar(event.uChar.AsciiChar);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::OnResize(const COORD& size)
|
|
{
|
|
if ((size.X != m_consoleScreenBufferSize.X) || (size.Y != m_consoleScreenBufferSize.Y))
|
|
{
|
|
SetConsoleScreenBufferSize(m_screenBufferHandle, m_consoleScreenBufferSize);
|
|
SetConsoleWindowInfo(m_screenBufferHandle, TRUE, &m_consoleWindow);
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::OnBackspace()
|
|
{
|
|
if (m_commandCursor > 0)
|
|
{
|
|
m_command.erase(--m_commandCursor, 1);
|
|
m_pConsole->ResetAutoCompletion();
|
|
DrawCommand();
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::OnTab()
|
|
{
|
|
const char* pCompletion;
|
|
|
|
pCompletion = m_pConsole->ProcessCompletion(m_command.c_str());
|
|
|
|
if (pCompletion)
|
|
{
|
|
m_command = pCompletion;
|
|
m_commandCursor = m_command.length();
|
|
DrawCommand();
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::OnReturn()
|
|
{
|
|
m_commandQueue.push_back(m_command);
|
|
m_command.clear();
|
|
m_pConsole->ResetAutoCompletion();
|
|
m_commandCursor = 0;
|
|
DrawCommand();
|
|
}
|
|
|
|
void CWindowsConsole::OnPgUp()
|
|
{
|
|
if (m_logBuffer.Scroll(-WINDOWS_CONSOLE_LOG_SCROLL_LINES))
|
|
{
|
|
m_dirtyCellBuffers |= eCBB_Log;
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::OnPgDn()
|
|
{
|
|
if (m_logBuffer.Scroll(WINDOWS_CONSOLE_LOG_SCROLL_LINES))
|
|
{
|
|
m_dirtyCellBuffers |= eCBB_Log;
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::OnLeft()
|
|
{
|
|
if (m_commandCursor > 0)
|
|
{
|
|
--m_commandCursor;
|
|
m_commandBuffer.SetCursor(m_screenBufferHandle, m_commandCursor + m_commandPromptLength);
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::OnUp()
|
|
{
|
|
OnHistory(m_pConsole->GetHistoryElement(true));
|
|
}
|
|
|
|
void CWindowsConsole::OnRight()
|
|
{
|
|
if (m_commandCursor < m_command.length())
|
|
{
|
|
++m_commandCursor;
|
|
m_commandBuffer.SetCursor(m_screenBufferHandle, m_commandCursor + m_commandPromptLength);
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::OnDown()
|
|
{
|
|
OnHistory(m_pConsole->GetHistoryElement(false));
|
|
}
|
|
|
|
void CWindowsConsole::OnDelete()
|
|
{
|
|
if (m_commandCursor < m_command.length())
|
|
{
|
|
m_command.erase(m_commandCursor, 1);
|
|
m_pConsole->ResetAutoCompletion();
|
|
DrawCommand();
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::OnChar(CHAR ch)
|
|
{
|
|
if ((ch >= ' ') && (ch <= '~'))
|
|
{
|
|
m_command.insert(m_commandCursor++, ch);
|
|
m_pConsole->ResetAutoCompletion();
|
|
DrawCommand();
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::OnHistory(const char* pHistoryElement)
|
|
{
|
|
if (pHistoryElement)
|
|
{
|
|
m_command = pHistoryElement;
|
|
}
|
|
else
|
|
{
|
|
m_command.clear();
|
|
}
|
|
|
|
m_commandCursor = m_command.length();
|
|
DrawCommand();
|
|
}
|
|
|
|
void CWindowsConsole::DrawCommand()
|
|
{
|
|
m_commandBuffer.Clear();
|
|
m_commandBuffer.PutText(0, 0, m_commandPrompt.c_str());
|
|
m_commandBuffer.PutText(m_commandPromptLength, 0, m_command);
|
|
m_commandBuffer.SetCursor(m_screenBufferHandle, m_commandCursor + m_commandPromptLength);
|
|
m_dirtyCellBuffers |= eCBB_Command;
|
|
}
|
|
|
|
void CWindowsConsole::GetMemoryUsage(ICrySizer* pSizer)
|
|
{
|
|
pSizer->Add(this);
|
|
pSizer->Add(m_command);
|
|
pSizer->Add(m_logLine);
|
|
pSizer->Add(m_pInputThread);
|
|
m_logBuffer.GetMemoryUsage(pSizer);
|
|
m_fullScreenBuffer.GetMemoryUsage(pSizer);
|
|
m_statusBuffer.GetMemoryUsage(pSizer);
|
|
m_commandBuffer.GetMemoryUsage(pSizer);
|
|
}
|
|
|
|
void CWindowsConsole::SetRequireDedicatedServer(bool value)
|
|
{
|
|
m_requireDedicatedServer = value;
|
|
}
|
|
|
|
void CWindowsConsole::SetHeader(const char* pHeader)
|
|
{
|
|
m_header = pHeader;
|
|
SetConsoleTitle(pHeader);
|
|
}
|
|
|
|
void CWindowsConsole::InputIdle()
|
|
{
|
|
if (m_pTimer)
|
|
{
|
|
CTimeValue now = m_pTimer->GetAsyncTime();
|
|
float timePassed = (now - m_lastUpdateTime).GetSeconds();
|
|
|
|
if (timePassed > 0.2F)
|
|
{
|
|
int nDots = ( int )(timePassed + 0.5) / 3;
|
|
int nDotsMax = m_statusBuffer.Width() - 2;
|
|
|
|
if (nDots > nDotsMax)
|
|
{
|
|
nDots = nDotsMax;
|
|
}
|
|
|
|
if (m_progressString.length() != nDots)
|
|
{
|
|
m_progressString.clear();
|
|
m_progressString.append(nDots, '.');
|
|
DrawStatus();
|
|
}
|
|
}
|
|
}
|
|
|
|
Repaint();
|
|
}
|
|
|
|
void CWindowsConsole::Lock()
|
|
{
|
|
m_lock.Lock();
|
|
}
|
|
|
|
void CWindowsConsole::Unlock()
|
|
{
|
|
m_lock.Unlock();
|
|
}
|
|
|
|
bool CWindowsConsole::TryLock()
|
|
{
|
|
return m_lock.TryLock();
|
|
}
|
|
|
|
void CWindowsConsole::Repaint()
|
|
{
|
|
if (m_dirtyCellBuffers)
|
|
{
|
|
if (m_dirtyCellBuffers & eCBB_Full)
|
|
{
|
|
m_fullScreenBuffer.Blit(m_screenBufferHandle);
|
|
m_dirtyCellBuffers &= ~eCBB_Full;
|
|
}
|
|
else if (m_dirtyCellBuffers & eCBB_Log)
|
|
{
|
|
m_logBuffer.Blit(m_screenBufferHandle);
|
|
m_dirtyCellBuffers &= ~eCBB_Log;
|
|
}
|
|
|
|
if (m_dirtyCellBuffers & eCBB_Status)
|
|
{
|
|
m_statusBuffer.Blit(m_screenBufferHandle);
|
|
m_dirtyCellBuffers &= ~eCBB_Status;
|
|
}
|
|
|
|
if (m_dirtyCellBuffers & eCBB_Command)
|
|
{
|
|
m_commandBuffer.Blit(m_screenBufferHandle);
|
|
m_dirtyCellBuffers &= ~eCBB_Command;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::DrawStatus()
|
|
{
|
|
const char* pStatusLeft = NULL;
|
|
const char* pStatusRight = NULL;
|
|
char bufferLeft[ 256 ];
|
|
char bufferRight[ 256 ];
|
|
|
|
// If we're scrolled, then the right size shows a scroll indicator.
|
|
if (m_logBuffer.IsScrolledUp())
|
|
{
|
|
m_logBuffer.FmtScrollStatus(sizeof bufferRight, bufferRight);
|
|
bufferRight[ sizeof bufferRight - 1 ] = 0;
|
|
pStatusRight = bufferRight;
|
|
}
|
|
|
|
if (!m_progressString.empty())
|
|
{
|
|
azsnprintf(bufferLeft, sizeof bufferLeft, " %s", m_progressString.c_str());
|
|
bufferLeft [sizeof bufferLeft - 1 ] = 0;
|
|
pStatusLeft = bufferLeft;
|
|
}
|
|
else if (m_OnUpdateCalled)
|
|
{
|
|
// Standard status display.
|
|
// Map name and game rules on the left.
|
|
// Current update rate and player count on the right.
|
|
|
|
const char* pMapName = m_pCVarSvMap->GetString();
|
|
|
|
const char* pMissionName = m_pCVarSvMission ? m_pCVarSvMission->GetString() : "";
|
|
azsnprintf(bufferLeft, sizeof bufferLeft, " mission: %s map:%s", pMissionName, pMapName);
|
|
|
|
bufferLeft[ sizeof bufferLeft - 1 ] = 0;
|
|
pStatusLeft = bufferLeft;
|
|
|
|
if (!pStatusRight)
|
|
{
|
|
float updateRate = 0.f;
|
|
|
|
if (m_pTimer != NULL)
|
|
{
|
|
updateRate = m_pTimer->GetFrameRate();
|
|
}
|
|
else
|
|
{
|
|
updateRate = 0.f;
|
|
}
|
|
|
|
char* pBufferRight = bufferRight;
|
|
char* const pBufferRightEnd = bufferRight + sizeof bufferRight;
|
|
|
|
azstrcpy(pBufferRight, AZ_ARRAY_SIZE(bufferRight), "| ");
|
|
pBufferRight += strlen(pBufferRight);
|
|
|
|
if (pBufferRight < pBufferRightEnd)
|
|
{
|
|
if (m_pConsole != NULL)
|
|
{
|
|
pBufferRight += azsnprintf(
|
|
pBufferRight,
|
|
pBufferRightEnd - pBufferRight,
|
|
"upd:%.1fms(%.2f..%.2f) " \
|
|
"rate:%.1f/s",
|
|
m_updStats.avgUpdateTime, m_updStats.minUpdateTime, m_updStats.maxUpdateTime,
|
|
updateRate);
|
|
}
|
|
else
|
|
{
|
|
cry_strcpy(pBufferRight, pBufferRightEnd - pBufferRight, "BUSY ");
|
|
}
|
|
}
|
|
|
|
bufferRight[ sizeof bufferRight - 1 ] = 0;
|
|
pStatusRight = bufferRight;
|
|
}
|
|
}
|
|
|
|
if (pStatusLeft == NULL)
|
|
{
|
|
pStatusLeft = "";
|
|
}
|
|
|
|
if (pStatusRight == NULL)
|
|
{
|
|
pStatusRight = "";
|
|
}
|
|
|
|
int rightWidth = strlen(pStatusRight);
|
|
|
|
m_statusBuffer.Clear();
|
|
m_statusBuffer.PutText(0, 0, pStatusLeft);
|
|
m_statusBuffer.PutText(-rightWidth, 0, pStatusRight);
|
|
m_dirtyCellBuffers |= eCBB_Status;
|
|
}
|
|
|
|
void CWindowsConsole::CleanUp()
|
|
{
|
|
Lock();
|
|
|
|
if (m_initialized)
|
|
{
|
|
if (m_pInputThread)
|
|
{
|
|
m_pInputThread->Cancel();
|
|
|
|
// The input thread may continue to lock before it gets our cancel event, so
|
|
// we need to release the lock until we confirm that it has canceled its operations.
|
|
Unlock();
|
|
m_pInputThread->WaitForThread();
|
|
Lock();
|
|
|
|
delete m_pInputThread;
|
|
m_pInputThread = NULL;
|
|
}
|
|
|
|
if (m_pConsole)
|
|
{
|
|
m_pConsole->RemoveOutputPrintSink(this);
|
|
}
|
|
|
|
m_pSystem = NULL;
|
|
m_pConsole = NULL;
|
|
m_pTimer = NULL;
|
|
m_pCVarSvMap = NULL;
|
|
m_pCVarSvGameRules = NULL;
|
|
m_inputBufferHandle = INVALID_HANDLE_VALUE;
|
|
m_screenBufferHandle = INVALID_HANDLE_VALUE;
|
|
m_initialized = false;
|
|
}
|
|
|
|
Unlock();
|
|
}
|
|
|
|
void CWindowsConsole::DrawFull()
|
|
{
|
|
for (DynArray< SConDrawCmd >::iterator iter = m_drawCmds.begin(); iter != m_drawCmds.end(); ++iter)
|
|
{
|
|
m_fullScreenBuffer.PutText(iter->x, iter->y, iter->text);
|
|
}
|
|
|
|
m_dirtyCellBuffers |= eCBB_Full;
|
|
}
|
|
|
|
CWindowsConsole::CCellBuffer::CCellBuffer(SHORT x, short y, SHORT w, SHORT h, SHORT lines, WCHAR emptyChar, uint8 defaultFgColor, uint8 defaultBgColor)
|
|
{
|
|
m_emptyCell.Char.UnicodeChar = emptyChar;
|
|
m_emptyCell.Attributes = s_colorTable[ defaultFgColor ] | (s_colorTable[ defaultBgColor ] << WINDOWS_CONSOLE_BGCOLOR_SHIFT);
|
|
m_attr = m_emptyCell.Attributes;
|
|
m_size.X = w;
|
|
m_size.Y = lines;
|
|
m_screenArea.Left = x;
|
|
m_screenArea.Top = y;
|
|
m_screenArea.Right = x + w - 1;
|
|
m_screenArea.Bottom = y + h - 1;
|
|
m_position.head = 0;
|
|
m_position.lines = 1;
|
|
m_position.wrap = 0;
|
|
m_position.offset = 0;
|
|
m_position.scroll = 0;
|
|
m_escape = false;
|
|
m_color = false;
|
|
m_buffer.resize(w * lines, m_emptyCell);
|
|
}
|
|
|
|
CWindowsConsole::CCellBuffer::~CCellBuffer()
|
|
{
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::PutText(int x, int y, const char* pMsg)
|
|
{
|
|
SPosition position;
|
|
|
|
position.head = m_position.head;
|
|
position.offset = x;
|
|
position.lines = y;
|
|
position.scroll = 0;
|
|
position.wrap = 0;
|
|
|
|
if (position.offset < 0)
|
|
{
|
|
position.offset += m_screenArea.Right - 1;
|
|
}
|
|
|
|
if (position.lines < 0)
|
|
{
|
|
position.lines += m_screenArea.Bottom - 1;
|
|
}
|
|
|
|
Print(pMsg, position);
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::Print(const char* pInszText)
|
|
{
|
|
Print(pInszText, m_position);
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::ClearCells(TBuffer::iterator pDst, TBuffer::iterator pDstEnd)
|
|
{
|
|
std::fill(pDst, pDstEnd, m_emptyCell);
|
|
}
|
|
|
|
bool CWindowsConsole::CCellBuffer::Scroll(SHORT numLines)
|
|
{
|
|
bool result = false;
|
|
SHORT newScroll = m_position.scroll + numLines;
|
|
SHORT maxScroll = m_position.lines - 1 - (m_screenArea.Bottom - m_screenArea.Top);
|
|
|
|
if (newScroll > maxScroll)
|
|
{
|
|
newScroll = maxScroll;
|
|
}
|
|
|
|
if (newScroll < 0)
|
|
{
|
|
newScroll = 0;
|
|
}
|
|
|
|
if (newScroll != m_position.scroll)
|
|
{
|
|
m_position.scroll = newScroll;
|
|
result = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::SetCursor(HANDLE hScreenBuffer, SHORT offset)
|
|
{
|
|
COORD position;
|
|
|
|
position.X = m_screenArea.Left + offset;
|
|
position.Y = m_screenArea.Top;
|
|
SetConsoleCursorPosition(hScreenBuffer, position);
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::AddCharacter(WCHAR ch, SPosition& position)
|
|
{
|
|
if (position.offset == m_size.X)
|
|
{
|
|
WrapLine(position);
|
|
}
|
|
|
|
int32 index = (((position.head + position.lines + m_size.Y - 1) % m_size.Y) * m_size.X) + position.offset;
|
|
|
|
CHAR_INFO& info = m_buffer[ index ];
|
|
|
|
info.Attributes = m_attr;
|
|
info.Char.UnicodeChar = ch;
|
|
++position.offset;
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::WrapLine(SPosition& position)
|
|
{
|
|
++position.wrap;
|
|
AdvanceLine(position);
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::NewLine()
|
|
{
|
|
NewLine(m_position);
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::NewLine(SPosition& position)
|
|
{
|
|
m_attr = m_emptyCell.Attributes;
|
|
position.wrap = 0;
|
|
AdvanceLine(position);
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::ClearLine(SPosition& position)
|
|
{
|
|
ClearCells(m_buffer.begin() + ((position.head + position.lines - position.wrap) % m_size.Y) * m_size.X, m_buffer.begin() + ((position.head + position.lines + 1) % m_size.Y) * m_size.X);
|
|
position.lines -= position.wrap;
|
|
position.wrap = 0;
|
|
position.offset = 0;
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::Tab(SPosition& position)
|
|
{
|
|
do
|
|
{
|
|
AddCharacter(' ', position);
|
|
} while (position.offset % WINDOWS_CONSOLE_TAB_SIZE);
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::Blit(HANDLE hScreenBuffer)
|
|
{
|
|
COORD src;
|
|
SMALL_RECT dst;
|
|
|
|
src.X = 0;
|
|
src.Y = (m_position.head + m_position.scroll) % m_size.Y;
|
|
dst = m_screenArea;
|
|
WriteConsoleOutput(hScreenBuffer, &*m_buffer.begin(), m_size, src, &dst);
|
|
|
|
if ((m_size.Y - src.Y) < (m_screenArea.Bottom - m_screenArea.Top + 1))
|
|
{
|
|
src.Y = 0;
|
|
dst.Top = dst.Bottom + 1;
|
|
dst.Bottom = m_screenArea.Bottom;
|
|
WriteConsoleOutput(hScreenBuffer, &*m_buffer.begin(), m_size, src, &dst);
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::AdvanceLine(SPosition& position)
|
|
{
|
|
position.offset = 0;
|
|
|
|
if (position.lines == m_size.Y)
|
|
{
|
|
position.head = (position.head + 1) % m_size.Y;
|
|
}
|
|
else
|
|
{
|
|
++position.lines;
|
|
|
|
if (position.lines > m_screenArea.Bottom - m_screenArea.Top + 1)
|
|
{
|
|
++position.scroll;
|
|
}
|
|
}
|
|
|
|
TBuffer::iterator start = m_buffer.begin() + ((position.head + position.lines + m_size.Y - 1) % m_size.Y) * m_size.X;
|
|
TBuffer::iterator end = start + m_size.X;
|
|
|
|
ClearCells(start, end);
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::SetFgColor(WORD color)
|
|
{
|
|
m_attr = (m_attr & ~WINDOWS_CONSOLE_COLOR_MASK) | s_colorTable[ color ];
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::Print(const char* pInszText, SPosition& position)
|
|
{
|
|
while (*pInszText)
|
|
{
|
|
switch (*pInszText)
|
|
{
|
|
case '$':
|
|
if (!m_escape)
|
|
{
|
|
m_color = true;
|
|
break;
|
|
}
|
|
case '\\':
|
|
m_escape = !m_escape;
|
|
if (m_escape)
|
|
{
|
|
break;
|
|
}
|
|
case 'n':
|
|
if (m_escape)
|
|
{
|
|
case '\n':
|
|
NewLine(position);
|
|
m_escape = false;
|
|
break;
|
|
}
|
|
case 'r':
|
|
if (m_escape)
|
|
{
|
|
case '\r':
|
|
ClearLine(position);
|
|
m_escape = false;
|
|
break;
|
|
}
|
|
case 't':
|
|
if (m_escape)
|
|
{
|
|
case '\t':
|
|
Tab(position);
|
|
m_escape = false;
|
|
break;
|
|
}
|
|
default:
|
|
if (m_color)
|
|
{
|
|
if (isdigit(*pInszText))
|
|
{
|
|
SetFgColor(*pInszText - '0');
|
|
}
|
|
|
|
m_color = false;
|
|
}
|
|
else
|
|
{
|
|
if (m_escape && (*pInszText != '\\'))
|
|
{
|
|
AddCharacter('\\', position);
|
|
}
|
|
AddCharacter(*pInszText, position);
|
|
}
|
|
m_escape = false;
|
|
break;
|
|
}
|
|
|
|
++pInszText;
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::GetMemoryUsage(ICrySizer* pSizer)
|
|
{
|
|
pSizer->Add(m_buffer);
|
|
}
|
|
|
|
bool CWindowsConsole::CCellBuffer::IsScrolledUp()
|
|
{
|
|
return (m_position.lines - m_position.scroll) > (m_screenArea.Bottom - m_screenArea.Top + 1);
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::FmtScrollStatus(uint32 size, char* pBuffer)
|
|
{
|
|
if (m_position.scroll)
|
|
{
|
|
azsnprintf(pBuffer, size, "| SCROLL: %.1f%%", 100.0F * static_cast< float >(m_position.scroll) / static_cast< float >(m_position.lines - (m_screenArea.Bottom - m_screenArea.Top + 1)));
|
|
}
|
|
else
|
|
{
|
|
cry_strcpy(pBuffer, size, "| SCROLL:TOP ");
|
|
}
|
|
}
|
|
|
|
void CWindowsConsole::CCellBuffer::Clear()
|
|
{
|
|
ClearCells(m_buffer.begin(), m_buffer.end());
|
|
}
|
|
|
|
SHORT CWindowsConsole::CCellBuffer::Width()
|
|
{
|
|
return m_screenArea.Right - m_screenArea.Left + 1;
|
|
}
|
|
|
|
|
|
CWindowsConsoleInputThread::CWindowsConsoleInputThread(CWindowsConsole& console)
|
|
: m_WindowsConsole(console)
|
|
{
|
|
m_handles[ eWH_Event ] = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
m_handles[ eWH_Console ] = m_WindowsConsole.m_inputBufferHandle;
|
|
}
|
|
|
|
CWindowsConsoleInputThread::~CWindowsConsoleInputThread()
|
|
{
|
|
CloseHandle(m_handles[ eWH_Event ]);
|
|
}
|
|
|
|
void CWindowsConsoleInputThread::Run()
|
|
{
|
|
bool cancelled = false;
|
|
|
|
do
|
|
{
|
|
DWORD inputRecordCount = 0;
|
|
DWORD waitResult;
|
|
|
|
waitResult = WaitForMultipleObjects(eWH_NumWaitHandles, m_handles, FALSE, 100);
|
|
|
|
switch (waitResult)
|
|
{
|
|
case WAIT_OBJECT_0 + eWH_Event:
|
|
|
|
cancelled = true;
|
|
break;
|
|
|
|
case WAIT_OBJECT_0 + eWH_Console:
|
|
|
|
ReadConsoleInput(m_WindowsConsole.m_inputBufferHandle, m_inputRecords, WINDOWS_CONSOLE_MAX_INPUT_RECORDS, &inputRecordCount);
|
|
|
|
// FALL THROUGH
|
|
|
|
case WAIT_TIMEOUT:
|
|
|
|
if (inputRecordCount || (m_WindowsConsole.m_dirtyCellBuffers && !m_WindowsConsole.m_OnUpdateCalled))
|
|
{
|
|
m_WindowsConsole.Lock();
|
|
|
|
if (inputRecordCount)
|
|
{
|
|
PINPUT_RECORD pInputRecordEnd = m_inputRecords + inputRecordCount;
|
|
|
|
for (PINPUT_RECORD pInputRecord = m_inputRecords; pInputRecord < pInputRecordEnd; ++pInputRecord)
|
|
{
|
|
m_WindowsConsole.OnConsoleInputEvent(*pInputRecord);
|
|
}
|
|
|
|
m_WindowsConsole.DrawCommand();
|
|
}
|
|
else
|
|
{
|
|
m_WindowsConsole.InputIdle();
|
|
}
|
|
|
|
m_WindowsConsole.Unlock();
|
|
}
|
|
|
|
break;
|
|
}
|
|
} while (!cancelled);
|
|
}
|
|
|
|
void CWindowsConsoleInputThread::Cancel()
|
|
{
|
|
SetEvent(m_handles[ eWH_Event ]);
|
|
}
|
|
|
|
#endif // def USE_WINDOWSCONSOLE
|