Черновой расчет загруженности процессора

Вторник, 8 Январь, 2008

CPU Load, CPU Usage, CPU Performance, загрузка процессора, да еще бы в процентах… Под Win9x-семейством — эти данные брались из ветки реестра HKEY_DYN, но с пришествием эры WinNT (2k, XP) что-то изменилось. Про Vista не буду, нет желания ее трогать.

Позавчера мне был нужен расчет загруженности процессора (для индикатора в ScreenDUO) и я его сделал. Вчерновую. Так как же оно работает?

Пробегаю по списку процессов (CreateToolhelp32Snapshot), узнаю сколько времени каждый процесс провел в kernel mode и сколько в user mode (GetProcessTimes), суммирую.

Вычитаю полученную сумму из предыдущего значения суммы времени. Беру время, прошедшее с момента получения последнего результата (таймер — вещь неточная, но мне хватает), перевожу его в наносекунды. Нахожу процентное соотношение времени, прошедшего с момента последнего тика таймера, и суммой времени, в течении которой процессы были заняты. Готово.

Метод неточный, но результаты выдает похожие на значение системного монитора. Работает под WinXP SP2, но непонятно, что будет выдавать на многоядерниках и при мультитрединге. Как обстоят дела с Win2k и Vista — я не знаю, под Win9x работать не будет.

#include “Tlhelp32.h”

const int updInterval = 10000; // msecs

double
 oldms = 0;
int GetDraftCpuUsage()
{
   
int n = 1;
   
double msecs = 0;
   HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
   
if (snap)      
   {
       
// walk through processes
       PROCESSENTRY32 pe;
       pe.dwSize =
sizeof(pe);
       Process32First(snap, &pe);
       
do 
       {
           FILETIME creation, exit, kernel, user;
           HANDLE ph = OpenProcess(PROCESS_QUERY_INFORMATION,
               
false, pe.th32ProcessID);
           
int r = GetProcessTimes(ph, &creation, &exit,
               &kernel, &user);
           CloseHandle(ph);

           if (r != 0)
           {
               
double tmp;

               tmp = kernel.dwLowDateTime;
               tmp += kernel.dwHighDateTime * (1 << 32);
               msecs += tmp;
               tmp = user.dwLowDateTime;
               tmp += user.dwHighDateTime * (1 << 32);    
               msecs += tmp;
           }

           n++;

       } while (Process32Next(snap, &pe) == TRUE);
   }

   if (!oldms)
       oldms = msecs;
   
   
int cpuUsage = (msecs - oldms) / (updInterval * 10000) * 100;
   oldms = msecs;
   
if (cpuUsage < 0) cpuUsage = 0;
   
if (cpuUsage > 100) cpuUsage = 100;
   
return cpuUsage;
}

По теме
- Почему поток захватывает 100% CPU?

Shogo и mouse-руль aka wheel

Понедельник, 10 Декабрь, 2007

Вероятно последняя статья про madding Shogo, т.к. больше там делать нечего. Камрад BLiTZ попросил сделать очередную доработку Shogo — приделать(binding) колесо от мышки, чтобы “скролировать” между видами вооружения.

Опыт уже есть, куда лезть понятно — …\ClientShellDLL\MouseMenu.cpp. Первое, с чем я сталкиваюсь, это перечисление параметров и возможностей мышки:


#define ID_MOUSELOOK        0
#define ID_LOOKSPRING        1
#define ID_INVERTYAXIS        2
#define ID_SENSITIVITY        3
#define ID_INPUTRATE        4
#define ID_LEFTBUTTON        5
#define ID_RIGHTBUTTON        6
#define ID_MIDDLEBUTTON        7
#define ID_UPWHEEL            8
#define ID_DOWNWHEEL        9
#define ID_BACK                10

Добавляю ID_UPWHEEL и ID_DOWNWHEEL, делаю себе крестик в Notes, что нужно где-то использовать. Тут же наталкиваюсь на следующую порцию — это присвоение глобальным переменным в конструкторе. Разбавляю своим кодом:

CMouseMenu::CMouseMenu() : CBaseMenu()
{
   m_nSecondColumn = 0;

   m_nLeftButtonSelection = 0;
   m_nRightButtonSelection = 0;
   m_nMiddleButtonSelection = 0;
   m_nUpWheelSelection = 0;
   m_nDownWheelSelection = 0;
}

Очередная пометка насчет глобальных переменных (они, вероятно, где-то в .h файле). Не заморачиваясь иду по файлу сверху-вниз. Натыкаюсь на инициализатор и начинаю его подробный ревьюинг.

DBOOL CMouseMenu::Init (CClientDE* pClientDE, CRiotMenu* pRiotMenu, CBaseMenu* pParent, int nScreenWidth, int nScreenHeight)
{

Инициализация глобальных переменных кодом команды unassigned. Колесо будет работать как кнопка, поэтому основное внимание уделяю работе с кнопками мышки. Пытаюсь делать по аналогии.

   m_nLeftButtonSelection = NUM_COMMANDS - 1;        // unassigned
   m_nMiddleButtonSelection = NUM_COMMANDS - 1;
   m_nRightButtonSelection = NUM_COMMANDS - 1;
   m_nUpWheelSelection = NUM_COMMANDS - 1;
   m_nDownWheelSelection = NUM_COMMANDS - 1;

Просканировав дальнейший кусок кода, выясняю, что здесь происходит загрузка байндинга кнопок из конфига.

// get button object names
// load the current button bindings

Интерес представил дефайн CONTROLTYPE_BUTTON, идем посмотреть.

#define CONTROLTYPE_BUTTON        8            // a mouse button

А что это у нас про wheel написано? Ага! Оказывается колёсико — это у нас ось-Z.

#define CONTROLTYPE_ZAXIS    3 // z-axis, such as a wheel on a mouse or a throttle on a joystick

Оформляю так:

       if (pObj->m_ObjectType == CONTROLTYPE_BUTTON)
       {
           
if (nButton == 0) SAFE_STRCPY(strLeftButton, pObj->m_ObjectName);
           
if (nButton == 1) SAFE_STRCPY(strRightButton, pObj->m_ObjectName);
           
if (nButton == 2) SAFE_STRCPY(strMiddleButton, pObj->m_ObjectName);

           nButton++;
       }

       if (pObj->m_ObjectType == CONTROLTYPE_ZAXIS)
       {
           SAFE_STRCPY(strWheel, pObj->m_ObjectName);
       }

Практически без затруднений пробежав по этому куску, я притормозил и глубоко задумался: колесо, как кнопка — одно, а действия у него два: вперед-назад. Перекур.

Камрад BLiTZ подкидывает одно из решений в FAQ’е по Shogo:

rangebind “##mouse” “Wheel” 10.000000 120.000000 “NextWeapon” -120.000000 -10.000000 “PrevWeapon”

…и многое проясняется. Одно устройство, одна кнопка. На кнопку (в данном случае — колесо) навешивается два экшена: один из которых сработает при отрицательной координате (колесо на себя), другой при положительной (колесо от себя). Я возвращаюсь немного обратно и разбавляю код загрузки настроек конфига следующим куском:

       if (stricmp (pBinding->strTriggerName, strWheel) == 0 && pBinding->pActionHead)
       {
           
if (pBinding->pActionHead->nRangeLow < 0)
               m_nDownWheelSelection = CommandToArrayPos (pBinding->pActionHead->nActionCode);
           
else
               m_nUpWheelSelection = CommandToArrayPos (pBinding->pActionHead->nActionCode);

           GameAction* pAction = pBinding->pActionHead->pNext;

           if (pAction)
           {
               
if (pBinding->pActionHead->nRangeLow < 0)
                   m_nDownWheelSelection = CommandToArrayPos (pAction->nActionCode);
               
else
                   m_nUpWheelSelection = CommandToArrayPos (pAction->nActionCode);
           }
       }

Первый if — проверка наличия экшена в биндинге + проверка наш ли это биндинг. Далее:
- Блок-if — забирает первый экшен.
- Next получает второй экшен, если он есть, иначе NULL.
- Проверяет, есть ли второй экшен.
- Забираем второй экшен.

Коды команд присваиваются глобальным переменным m_nDownWheelSelection и m_nUpWheelSelection.

Немного балую себя и добавляю строчку:

pClientDE->CPrint(“ZOOM MOD v1.0rc2 — http://www.nikvoronin.com/shogo/”);

…себя не похвалишь… Заодно, будет видно — отработал мод или нет.

Меню настроек мышки в Shogo отличается по принципу работы от меню настроек клавиатуры. Здесь экшен выбирается из списка клавишами влево-вправо, т.е. приходится перебирать несколько экшенов, пока не доберешься до нужного. Перехожу к потрошению методов CMouseMenu::Left() и CMouseMenu::Right().

По аналогии с обработкой кнопок мыши добавляю обработчик колесо-вверх, колесо-вниз. BindButtonToCommand заменяю на заглушку BindWheelToCommand.

else if (m_nSelection == ID_MIDDLEBUTTON)
{
ChangeButtonSettingSurface (m_nSelection, &m_nMiddleButtonSelection, -1);
BindButtonToCommand (2, m_nMiddleButtonSelection);
}
else if (m_nSelection == ID_UPWHEEL)
{
ChangeButtonSettingSurface (m_nSelection, &m_nUpWheelSelection, -1);
BindWheelToCommand();
}
else if (m_nSelection == ID_DOWNWHEEL)
{
ChangeButtonSettingSurface (m_nSelection, &m_nDownWheelSelection, -1);
BindWheelToCommand();
}

CMouseMenu::Right() отличается от Left только значением параметра при вызове ChangeButtonSettingSurface.

Далее идут методы отрисовки меню (Draw), загрузки сурфэйсов (LoadSurface). Доделываю по аналогии с кнопками мышки и тут хлоп… ай-я-яй, как нехорошо! Использовать магические числа в движке. В циклах загрузки/отрисовки пунктов меню используется число 9, равное числу настраиваемых параметров мышки. После моих инъекций их стало 11. Ревьюю код и ввожу дефайн (9-ка встречается далеко не в одном месте):

for (int i = m_nTopItem; i < NUM_MOUSE_ID; i++)
{
if (m_MouseSettings[i].hMenuItem)
{

Это были мелочи, а теперь… я добрался до самого главного — метода:

DBOOL CMouseMenu::BindButtonToCommand (int nButton, int nSelection)

Копи-пастом добавляю BindWheelToCommand и глубоко задумываюсь. При назначении кнопок все просто: мы добавляем один биндинг для одной кнопки, иначе говоря — один пункт меню, одна кнопка, один биндинг в котором один экшен. В случае с колесом ситуация иная — два пункта меню, одна кнопка (колесо), один биндинг в котором два экшена.

Биндинги назначаются через исполнение команды консоли: m_pClientDE->RunConsoleString (str);. В случае с колесом, я не смогу сформировать строку консоли (следовательно, назначить биндинг) пока пользователь не задаст оба пункта меню. Хотя, с другой стороны, пользователь может захотеть забайндить только одно направление колеса мышки…

Несмотря на мутноватые размышление, в голове понемногу выстраивается последовательность вызовов и назначений. Я начинаю кодить.

Кажется все так. Билд. Копирование DLL. Запуск. Странности с биндингом: настройки загружаются нормально, если поменять ручками в конфиге — все равно нормально, если менять из меню — на оба направления мышки биндится один и тот же экшен. Ревьюю код. Час, другой… Хочется курить и ругаться матом. Что, впрочем, и делаю.

Засаду устроил метод CommandName из ClientUtilities.cpp, объявив переменную buffer как static. Оставляю “как есть” и добавляю в код SAFE_STRCPY.

   // remove old binding and set new binding

   char str[128];
   sprintf (str,
“rangebind \”%s\” \”%s\” 0 0 \”\” 0 0 \”\”", pObj->m_DeviceName, pObj->m_ObjectName);
   m_pClientDE->RunConsoleString (str);

   // create then set new binding

   char strUp[128] = {0};
   
char strDown[128] = {0};

   if (m_nDownWheelSelection != NUM_COMMANDS - 1 &&
       m_nUpWheelSelection != NUM_COMMANDS - 1 )
   {
       
       SAFE_STRCPY(strUp, CommandName (g_CommandArray[m_nUpWheelSelection].nCommandID));
       SAFE_STRCPY(strDown, CommandName (g_CommandArray[m_nDownWheelSelection].nCommandID));
       sprintf (
               str,
               
“rangebind \”%s\” \”%s\” 10.000000 120.000000 \”%s\” -120.000000 -10.000000 \”%s\”",
               pObj->m_DeviceName,
               pObj->m_ObjectName,
               strUp,
               strDown
               );
   }
   
else
       if (m_nDownWheelSelection != NUM_COMMANDS - 1)
       {
           SAFE_STRCPY(strDown, CommandName (g_CommandArray[m_nDownWheelSelection].nCommandID));
           sprintf (
                   str,
                   
“rangebind \”%s\” \”%s\” -120.000000 -10.000000 \”%s\”",
                   pObj->m_DeviceName,
                   pObj->m_ObjectName,
                   strDown
                   );
       }
       
else
           if (m_nUpWheelSelection != NUM_COMMANDS - 1)
           {
               SAFE_STRCPY(strUp, CommandName (g_CommandArray[m_nUpWheelSelection].nCommandID));
               sprintf (
                       str,
                       
“rangebind \”%s\” \”%s\” 120.000000 10.000000 \”%s\”",
                       pObj->m_DeviceName,
                       pObj->m_ObjectName,
                       strUp
                       );
           }

   m_pClientDE->RunConsoleString (str);

Очередная сборка. Копирование. Запуск. Работает.

Я доволен, камрад BLiTZ в аналогичном состоянии, идем курить.

Исходники Shogo Zoom RC2 выложены на Shogo Madding.

По теме
- Исходники Shogo Zoom
- Включаем zoom в Shogo: Mobile Armor Division
- Shogo Madding

Исходники Shogo Zoom

Пятница, 30 Ноябрь, 2007

Собрал и выложил исходники Shogo Zoom.

Два архива: в одном — готовые для игры DLL, в другом — исходники ClientRes и поправленный код ClientShellDLL. Вполне возможно, что перед внедрением в игру, придется накатить патч до версии 2.2.

По теме
- Исходники: shogo2.2_zoom_src.zip
- DLL-библиотеки: shogo2.2_zoom_dll.zip
- Включаем zoom в Shogo: Mobile Armor Division
- PlanetShogo