haxis/Source/UnrealProject/External/Metrics.cpp

362 lines
8.2 KiB
C++

#include "UnrealProject.h"
#if WITH_EDITOR
#include "Metrics.hpp"
//#include <append>
#include <timer>
#include <fstream>
#include <unordered_map>
#include <unordered_set>
#include <chrono>
#include <algorithm>
#if PLATFORM_SPECIFIC_WIN == 0
#include "AllowWindowsPlatformTypes.h"
#include <Windows.h>
#endif
using namespace jlib;
using namespace std;
using namespace std::chrono;
#include "Events.hpp"
#include <type_traits>
namespace Metrics
{
ofstream writeStream;
timer writeTime;
uint32_t lastWriteTime;
unordered_map<uint8_t, PlayerHandle*> players;
uint32_t CurrentTime()
{
return uint32_t(writeTime.milliseconds());
}
uint32_t TimeOffset()
{
const uint32_t currentTime = uint32_t(writeTime.milliseconds());
const uint32_t offset = currentTime - lastWriteTime;
lastWriteTime = currentTime;
return offset;
}
wstring FormatNumber(uint16_t number, size_t desiredLength)
{
wstring result = wstring() + number;
while (result.size() < desiredLength)
result = wstring(L"0") + result;
return result;
}
template<typename Event> void WriteEvent(const Event& event)
{
writeStream.write((const char*)&event, streamsize(sizeof(Event)));
}
void WriteBinary(void* addr, size_t size)
{
writeStream.write((const char*)addr, size);
}
void Clear()
{
for (auto iter = players.begin(); iter != players.end(); iter++)
delete iter->second;
players.clear();
}
}
bool Metrics::StartSession(const wstring& filePath)
{
EndSession();
time_t timePoint = system_clock::to_time_t(std::chrono::system_clock::now());
const char* timeData = ctime(&timePoint);
wstring dateTime = convert_cstring_type<wchar_t, char>(timeData);
wstring dateTimeFiltered;
for (size_t i = 0; i < dateTime.size(); i++)
{
if (dateTime[i] == L'\n' || dateTime[i] == L'\r')
continue;
dateTimeFiltered += dateTime[i];
}
dateTime = dateTimeFiltered;
wstring sessionName = L"MetricsSession";
#if PLATFORM_SPECIFIC_WIN == 0
sessionName.clear();
sessionName.resize(256);
DWORD length = 256;
GetComputerName(&sessionName[0], &length);
sessionName.resize(length);
wstring sessionNameFiltered;
for (size_t i = 0; i < sessionName.size(); i++)
{
wchar_t character = sessionName[i];
if ((character >= L'0' && character <= L'9') || (character >= L'a' && character <= L'z') || (character >= L'A' && character <= L'Z') || character == L'-' || character == L'_')
sessionNameFiltered += character;
}
sessionName = sessionNameFiltered;
#endif
wstring fileName = sessionName + L" - " + dateTime + L".hms";
replace(fileName.begin(), fileName.end(), L':', L'-');
writeStream.open(convert_string_type<char, wchar_t>(filePath + fileName), ios::out | ios::binary | ios::trunc);
writeTime.restart();
lastWriteTime = 0;
// Write the header
writeStream.write("HMS", 3);
return writeStream.is_open();
}
bool Metrics::EndSession()
{
if (writeStream.is_open())
{
WriteEvent(SessionEnd(TimeOffset()));
Clear();
writeStream.close();
return true;
}
Clear();
return false;
}
struct Metrics::PlayerHandleSet
{
static void Enable(PlayerHandle& handle, uint8_t id, uint8_t team)
{
handle.m_id = id;
handle.m_isValid = true;
handle.m_team = team;
}
};
void Metrics::SetMapData(float minX, float maxX, float minY, float maxY)
{
if (!writeStream.is_open())
return;
WriteEvent(MapData(minX, maxX, minY, maxY));
}
void Metrics::SetMapImage(uint8_t* ptr, uint32_t width, uint32_t height)
{
if (!writeStream.is_open())
return;
WriteEvent(MapImage(width, height));
WriteBinary(ptr, width * height * 4);
}
Metrics::PlayerHandle::PlayerHandle() : m_isValid(false), m_id(-1), m_team(-1), m_updateTime(0), m_alive(false), m_health(INT_MAX), m_mana(INT_MAX), m_maxHealth(INT_MAX), m_maxMana(INT_MAX), m_level(255), m_playerX(FLT_MAX), m_playerY(FLT_MAX)
{
}
void Metrics::PlayerHandle::OnPlayerSpawn(float playerX, float playerY)
{
if (!writeStream.is_open() || !m_isValid)
return;
if (m_alive)
return;
m_alive = true;
m_playerX = playerX;
m_playerY = playerY;
WriteEvent(PlayerSpawn(TimeOffset(), m_id, playerX, playerY));
}
void Metrics::PlayerHandle::OnPlayerUpdate(float playerX, float playerY)
{
if (!writeStream.is_open() || !m_isValid)
return;
if (!m_alive)
return;
// No need to write an event if the player hasnt moved
if (fabsf(playerX - m_playerX) < 0.01f && fabsf(playerY - m_playerY) < 0.01f)
return;
// Only update position 30 times per second
const uint32_t currentTime = CurrentTime();
if (currentTime - m_updateTime < 33)
return;
m_updateTime = currentTime;
m_playerX = playerX;
m_playerY = playerY;
WriteEvent(PlayerUpdate(TimeOffset(), m_id, playerX, playerY));
}
void Metrics::PlayerHandle::OnPlayerDie(uint8_t source)
{
if (!writeStream.is_open() || !m_isValid)
return;
if (!m_alive)
return;
m_alive = false;
WriteEvent(PlayerDie(TimeOffset(), m_id, source));
}
void Metrics::PlayerHandle::OnPlayerCast(uint32_t ability)
{
if (!writeStream.is_open() || !m_isValid)
return;
if (!m_alive)
return;
WriteEvent(PlayerCast(TimeOffset(), m_id, ability));
}
void Metrics::PlayerHandle::OnPlayerHealth(int32_t health)
{
if (!writeStream.is_open() || !m_isValid)
return;
if (!m_alive)
return;
// No need to write an event if the player hasnt lost/gained health
if (m_health == health)
return;
m_health = health;
WriteEvent(PlayerHealth(TimeOffset(), m_id, health));
}
void Metrics::PlayerHandle::OnPlayerMana(int32_t mana)
{
if (!writeStream.is_open() || !m_isValid)
return;
if (!m_alive)
return;
// No need to write an event if the player hasnt lost/gained mana
if (m_mana == mana)
return;
m_mana = mana;
WriteEvent(PlayerMana(TimeOffset(), m_id, mana));
}
void Metrics::PlayerHandle::OnPlayerMaxHealth(int32_t maxHealth)
{
if (!writeStream.is_open() || !m_isValid)
return;
if (!m_alive)
return;
// No need to write an event if the player hasnt changed max health
if (m_maxHealth == maxHealth)
return;
m_maxHealth = maxHealth;
WriteEvent(PlayerMaxHealth(TimeOffset(), m_id, maxHealth));
}
void Metrics::PlayerHandle::OnPlayerMaxMana(int32_t maxMana)
{
if (!writeStream.is_open() || !m_isValid)
return;
if (!m_alive)
return;
// No need to write an event if the player hasnt changed max mana
if (m_maxMana == maxMana)
return;
m_maxMana = maxMana;
WriteEvent(PlayerMaxMana(TimeOffset(), m_id, maxMana));
}
void Metrics::PlayerHandle::OnPlayerDealDamage(uint32_t ability, int32_t damage)
{
if (!writeStream.is_open() || !m_isValid)
return;
if (!m_alive)
return;
WriteEvent(PlayerDealDamage(TimeOffset(), m_id, ability, damage));
}
void Metrics::PlayerHandle::OnPlayerLevel(uint8_t level)
{
if (!writeStream.is_open() || !m_isValid)
return;
if (m_level == level)
return;
m_level = level;
WriteEvent(PlayerLevel(TimeOffset(), m_id, level));
}
bool Metrics::PlayerHandle::IsValid() const
{
return m_isValid;
}
uint8_t Metrics::PlayerHandle::Id() const
{
return m_id;
}
uint8_t Metrics::PlayerHandle::Team() const
{
return m_team;
}
Metrics::PlayerHandle& Metrics::RegisterPlayer(const wstring& name, uint8_t team)
{
static PlayerHandle errorHandle;
if (!writeStream.is_open())
return errorHandle;
const uint8_t id = uint8_t(players.size() + 1);
PlayerHandle* newHandle = new PlayerHandle();
PlayerHandleSet::Enable(*newHandle, id, team);
players.emplace(id, newHandle);
wstring copy = name;
if (copy.size() > 128)
copy.resize(128);
else if (copy.empty())
copy = L"UNKNOWN PLAYER";
WriteEvent(PlayerName(id, (uint16_t)copy.size()));
WriteBinary((void*)copy.data(), copy.size() * sizeof(wstring::value_type));
WriteEvent(PlayerTeam(id, team));
return *newHandle;
}
void Metrics::RegisterAbility(uint32_t ability, const std::wstring& name)
{
if (!writeStream.is_open())
return;
wstring copy = name;
if (copy.size() > 128)
copy.resize(128);
else if (copy.empty())
copy = L"UNKNOWN ABILITY";
WriteEvent(AbilityName(ability, (uint16_t)copy.size()));
WriteBinary((void*)copy.data(), copy.size() * sizeof(wstring::value_type));
}
#endif