First pass idLib conversion from hex rays4.
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
#include "analysisclient.h"
|
||||
|
||||
#ifdef nullptr
|
||||
#undef nullptr
|
||||
#endif
|
||||
#ifdef snprintf
|
||||
#undef snprintf
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
|
||||
idCVar mq_analysisReportRate(
|
||||
"mq_analysisReportRate", "1", CVAR_INTEGER,
|
||||
"Control the rate at which asserts and map stats get reported.");
|
||||
idCVar mq_enable(
|
||||
"mq_enable", "0", CVAR_BOOL,
|
||||
"Enables the analysis client");
|
||||
|
||||
idAnalysisClient analysisClient;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string CurrentTimestamp() {
|
||||
std::time_t now = std::time(0);
|
||||
std::tm utcTime = {};
|
||||
gmtime_s(&utcTime, &now);
|
||||
char buffer[32] = {};
|
||||
std::strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", &utcTime);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string EventTimestamp(const char* timestamp) {
|
||||
return timestamp == 0 || timestamp[0] == 0
|
||||
? CurrentTimestamp() : std::string(timestamp);
|
||||
}
|
||||
|
||||
int NextReportTime() {
|
||||
const int seconds = std::max(1, mq_analysisReportRate.GetInteger());
|
||||
return Sys_Milliseconds() + seconds * 1000;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
idAnalysisClient::idAnalysisClient()
|
||||
: idMQClientThread(), reportTime(0), channel(nullptr), currentMap(),
|
||||
assertMutex(), mapLoadMutex(), viewNoteMutex(), pendingAsserts(),
|
||||
pendingMapLoads(), pendingViewNotes() {
|
||||
}
|
||||
|
||||
idAnalysisClient::~idAnalysisClient() {
|
||||
StopThread(true);
|
||||
}
|
||||
|
||||
void idAnalysisClient::StartMessageSystem() {
|
||||
if (mq_enable.GetBool()) {
|
||||
StartThread("AMQP idAnalysisClient >> Server");
|
||||
}
|
||||
}
|
||||
|
||||
void idAnalysisClient::StopMessageSystem() {
|
||||
StopThread(true);
|
||||
}
|
||||
|
||||
void idAnalysisClient::SetCurrentMap(const char* mapName) {
|
||||
currentMap = mapName == nullptr ? "" : mapName;
|
||||
}
|
||||
|
||||
void idAnalysisClient::QueueAssert(const idreports::AssertReport& report,
|
||||
const char* timestamp) {
|
||||
idScopedCriticalSection lock(assertMutex);
|
||||
pendingEvent_t<idreports::AssertReport> event;
|
||||
event.timestamp = EventTimestamp(timestamp);
|
||||
event.message = report;
|
||||
pendingAsserts.push_back(event);
|
||||
}
|
||||
|
||||
void idAnalysisClient::QueueMapLoad(const idreports::MapReport& report,
|
||||
const char* timestamp) {
|
||||
idScopedCriticalSection lock(mapLoadMutex);
|
||||
pendingEvent_t<idreports::MapReport> event;
|
||||
event.timestamp = EventTimestamp(timestamp);
|
||||
event.message = report;
|
||||
pendingMapLoads.push_back(event);
|
||||
}
|
||||
|
||||
void idAnalysisClient::QueueViewNote(const idreports::ViewNoteReport& report,
|
||||
const char* timestamp) {
|
||||
idScopedCriticalSection lock(viewNoteMutex);
|
||||
pendingEvent_t<idreports::ViewNoteReport> event;
|
||||
event.timestamp = EventTimestamp(timestamp);
|
||||
event.message = report;
|
||||
pendingViewNotes.push_back(event);
|
||||
}
|
||||
|
||||
int idAnalysisClient::NumPendingAsserts() {
|
||||
idScopedCriticalSection lock(assertMutex);
|
||||
return static_cast<int>(pendingAsserts.size());
|
||||
}
|
||||
|
||||
int idAnalysisClient::NumPendingMapLoads() {
|
||||
idScopedCriticalSection lock(mapLoadMutex);
|
||||
return static_cast<int>(pendingMapLoads.size());
|
||||
}
|
||||
|
||||
int idAnalysisClient::NumPendingViewNotes() {
|
||||
idScopedCriticalSection lock(viewNoteMutex);
|
||||
return static_cast<int>(pendingViewNotes.size());
|
||||
}
|
||||
|
||||
void idAnalysisClient::PreRun() {
|
||||
channel = connection.GetChannel();
|
||||
if (channel != nullptr) {
|
||||
channel->ExchangeDeclare(idStr("idtech5"), idStr("topic"), true,
|
||||
false);
|
||||
}
|
||||
reportTime = NextReportTime();
|
||||
}
|
||||
|
||||
void PublishEvent(idreports::LogEvent& logEvent, const char* timestamp,
|
||||
const idreports::LogEvent_Severity severity, const char* message,
|
||||
google::protobuf::MessageLite& outgoing, const char* messageType,
|
||||
idMQChannel* channel, const char* routingKey) {
|
||||
logEvent.Clear();
|
||||
logEvent.set_timestamp(timestamp == nullptr ? "" : timestamp);
|
||||
logEvent.set_severity(severity);
|
||||
logEvent.set_message(message == nullptr ? "" : message);
|
||||
logEvent.set_datatype(messageType == nullptr ? "" : messageType);
|
||||
|
||||
std::string outgoingData;
|
||||
outgoing.SerializePartialToString(&outgoingData);
|
||||
logEvent.set_data(outgoingData.data(), outgoingData.size());
|
||||
|
||||
std::string envelope;
|
||||
logEvent.SerializePartialToString(&envelope);
|
||||
if (channel != nullptr) {
|
||||
channel->BasicPublish(idStr("idtech5"),
|
||||
idStr(routingKey == nullptr ? "" : routingKey), false, false,
|
||||
envelope.data(), static_cast<unsigned int>(envelope.size()));
|
||||
}
|
||||
logEvent.Clear();
|
||||
}
|
||||
|
||||
void idAnalysisClient::ThreadSlice() {
|
||||
if (reportTime >= Sys_Milliseconds()) {
|
||||
Sleep(25);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<pendingEvent_t<idreports::AssertReport> > asserts;
|
||||
std::vector<pendingEvent_t<idreports::MapReport> > mapLoads;
|
||||
std::vector<pendingEvent_t<idreports::ViewNoteReport> > viewNotes;
|
||||
{
|
||||
idScopedCriticalSection lock(assertMutex);
|
||||
asserts.swap(pendingAsserts);
|
||||
}
|
||||
{
|
||||
idScopedCriticalSection lock(mapLoadMutex);
|
||||
mapLoads.swap(pendingMapLoads);
|
||||
}
|
||||
{
|
||||
idScopedCriticalSection lock(viewNoteMutex);
|
||||
viewNotes.swap(pendingViewNotes);
|
||||
}
|
||||
|
||||
idreports::LogEvent logEvent;
|
||||
for (std::size_t index = 0; index < asserts.size(); ++index) {
|
||||
char message[1200];
|
||||
std::snprintf(message, sizeof(message), "Hit an assert in %s",
|
||||
asserts[index].message.mapname().c_str());
|
||||
PublishEvent(logEvent, asserts[index].timestamp.c_str(),
|
||||
idreports::LogEvent_Severity_SEV_ERROR, message,
|
||||
asserts[index].message, "AssertReport", channel,
|
||||
"idtech5.events.assert_report");
|
||||
}
|
||||
for (std::size_t index = 0; index < mapLoads.size(); ++index) {
|
||||
char message[1200];
|
||||
std::snprintf(message, sizeof(message), "%s loaded the map %s",
|
||||
mapLoads[index].message.username().c_str(),
|
||||
mapLoads[index].message.mapname().c_str());
|
||||
PublishEvent(logEvent, mapLoads[index].timestamp.c_str(),
|
||||
idreports::LogEvent_Severity_SEV_WARNING, message,
|
||||
mapLoads[index].message, "MapReport", channel,
|
||||
"idtech5.events.map_load");
|
||||
}
|
||||
for (std::size_t index = 0; index < viewNotes.size(); ++index) {
|
||||
char message[1200];
|
||||
std::snprintf(message, sizeof(message),
|
||||
"%s published a viewnote for %s",
|
||||
viewNotes[index].message.username().c_str(),
|
||||
viewNotes[index].message.mappath().c_str());
|
||||
PublishEvent(logEvent, viewNotes[index].timestamp.c_str(),
|
||||
idreports::LogEvent_Severity_SEV_INFO, message,
|
||||
viewNotes[index].message, "ViewNoteReport", channel,
|
||||
"idtech5.events.viewnote");
|
||||
}
|
||||
reportTime = NextReportTime();
|
||||
Sleep(250);
|
||||
}
|
||||
|
||||
void idAnalysisClient::OnThreadTerminate() {
|
||||
channel = nullptr;
|
||||
idLib::Printf("idAnalysisClient::OnThreadTerminate \n");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include "idlib/networking/amqp/mqmessaging.h"
|
||||
#include "idlib/networking/protocols/reports.pb.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct mapLoadEvent_t {
|
||||
const char* timestamp;
|
||||
idreports::MapReport message;
|
||||
};
|
||||
|
||||
struct viewNoteEvent_t {
|
||||
const char* timestamp;
|
||||
idreports::ViewNoteReport message;
|
||||
};
|
||||
|
||||
struct assertEvent_t {
|
||||
const char* timestmap; // Original spelling retained for recovered ABI users.
|
||||
idreports::AssertReport message;
|
||||
};
|
||||
|
||||
struct idUniqueWarning {
|
||||
idStr message;
|
||||
unsigned int count;
|
||||
};
|
||||
|
||||
struct viewNoteData_t {
|
||||
idStr bugTitle;
|
||||
idStr taskType;
|
||||
idStr reproSteps;
|
||||
idStr details;
|
||||
idStr severity;
|
||||
idStr priority;
|
||||
idStr attachmentFilename;
|
||||
idStr gameName;
|
||||
bool isMultiplayer;
|
||||
int buildNumberMajor;
|
||||
int buildNumberMinor;
|
||||
idStr vt_filePath;
|
||||
idStr vt_filePathOverride;
|
||||
idStr mapName;
|
||||
};
|
||||
|
||||
class idAnalysisClient : public idMQClientThread {
|
||||
public:
|
||||
idAnalysisClient();
|
||||
~idAnalysisClient() override;
|
||||
|
||||
void StartMessageSystem() override;
|
||||
void StopMessageSystem() override;
|
||||
void PreRun() override;
|
||||
void ThreadSlice() override;
|
||||
void OnThreadTerminate() override;
|
||||
|
||||
void SetCurrentMap(const char* mapName);
|
||||
void QueueAssert(const idreports::AssertReport& report,
|
||||
const char* timestamp = 0);
|
||||
void QueueMapLoad(const idreports::MapReport& report,
|
||||
const char* timestamp = 0);
|
||||
void QueueViewNote(const idreports::ViewNoteReport& report,
|
||||
const char* timestamp = 0);
|
||||
|
||||
int NumPendingAsserts();
|
||||
int NumPendingMapLoads();
|
||||
int NumPendingViewNotes();
|
||||
|
||||
int reportTime;
|
||||
idMQChannel* channel;
|
||||
idStrStatic<1024> currentMap;
|
||||
idSysMutex assertMutex;
|
||||
idSysMutex mapLoadMutex;
|
||||
idSysMutex viewNoteMutex;
|
||||
|
||||
private:
|
||||
template<class T>
|
||||
struct pendingEvent_t {
|
||||
std::string timestamp;
|
||||
T message;
|
||||
};
|
||||
|
||||
std::vector<pendingEvent_t<idreports::AssertReport> > pendingAsserts;
|
||||
std::vector<pendingEvent_t<idreports::MapReport> > pendingMapLoads;
|
||||
std::vector<pendingEvent_t<idreports::ViewNoteReport> > pendingViewNotes;
|
||||
};
|
||||
|
||||
extern idCVar mq_enable;
|
||||
extern idCVar mq_analysisReportRate;
|
||||
extern idAnalysisClient analysisClient;
|
||||
|
||||
void PublishEvent(idreports::LogEvent& logEvent, const char* timestamp,
|
||||
idreports::LogEvent_Severity severity, const char* message,
|
||||
google::protobuf::MessageLite& outgoing, const char* messageType,
|
||||
idMQChannel* channel, const char* routingKey);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
// The complete allocator implementation survived in Doom 3 BFG's Heap.h.
|
||||
// Tungsten split the same fixed-block and dynamic-block templates into this
|
||||
// path; retain that source-level include path while using the vetted PC code.
|
||||
#include "idlib/precompiled.h"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "../math/vector.h"
|
||||
|
||||
// Minimal tungsten ABI facade used while the BFG idBounds implementation is
|
||||
// still the baseline for the larger geometry subsystem.
|
||||
class idBounds {
|
||||
public:
|
||||
idVec3 b[2];
|
||||
|
||||
const idVec3& operator[](const int index) const {
|
||||
return b[index];
|
||||
}
|
||||
|
||||
idVec3& operator[](const int index) {
|
||||
return b[index];
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(idBounds) == 24, "Recovered idBounds layout changed");
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "bounds2d.h"
|
||||
|
||||
bool idBounds2D::AddPoint(const idVec2& point) {
|
||||
bool expanded = false;
|
||||
|
||||
if (point.x < bounds[0].x) {
|
||||
bounds[0].x = point.x;
|
||||
expanded = true;
|
||||
}
|
||||
if (point.x > bounds[1].x) {
|
||||
bounds[1].x = point.x;
|
||||
expanded = true;
|
||||
}
|
||||
if (point.y < bounds[0].y) {
|
||||
bounds[0].y = point.y;
|
||||
expanded = true;
|
||||
}
|
||||
if (point.y > bounds[1].y) {
|
||||
bounds[1].y = point.y;
|
||||
expanded = true;
|
||||
}
|
||||
|
||||
return expanded;
|
||||
}
|
||||
|
||||
bool idBounds2D::ContainsPoint(const idVec2& point) const {
|
||||
return point.x >= bounds[0].x
|
||||
&& point.x <= bounds[1].x
|
||||
&& point.y >= bounds[0].y
|
||||
&& point.y <= bounds[1].y;
|
||||
}
|
||||
|
||||
bool idBounds2D::IntersectBounds(const idBounds2D& other) const {
|
||||
if (&other == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other.bounds[0].x <= bounds[1].x
|
||||
&& other.bounds[1].x >= bounds[0].x
|
||||
&& other.bounds[0].y <= bounds[1].y
|
||||
&& other.bounds[1].y >= bounds[0].y;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "../math/vector.h"
|
||||
|
||||
// tungsten.exe.h type 13101.
|
||||
class idBounds2D {
|
||||
public:
|
||||
idVec2 bounds[2];
|
||||
|
||||
bool AddPoint(const idVec2& point);
|
||||
bool ContainsPoint(const idVec2& point) const;
|
||||
bool IntersectBounds(const idBounds2D& other) const;
|
||||
|
||||
const idVec2& operator[](const int index) const {
|
||||
return bounds[index];
|
||||
}
|
||||
|
||||
idVec2& operator[](const int index) {
|
||||
return bounds[index];
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(idBounds2D) == 16, "Recovered idBounds2D layout changed");
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "bounds.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
class idBoundsShort {
|
||||
public:
|
||||
std::int16_t b[2][3];
|
||||
|
||||
void SetBounds(const idBounds& bounds) {
|
||||
for (int side = 0; side < 2; ++side) {
|
||||
for (int axis = 0; axis < 3; ++axis) {
|
||||
const int integerValue = static_cast<int>(bounds[side][axis]);
|
||||
b[side][axis] = static_cast<std::int16_t>(std::max(
|
||||
static_cast<int>(std::numeric_limits<std::int16_t>::min()),
|
||||
std::min(
|
||||
static_cast<int>(std::numeric_limits<std::int16_t>::max()),
|
||||
integerValue
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
idBounds ToBounds() const {
|
||||
idBounds result;
|
||||
for (int side = 0; side < 2; ++side) {
|
||||
for (int axis = 0; axis < 3; ++axis) {
|
||||
result[side][axis] = static_cast<float>(b[side][axis]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IntersectsBounds(const idBoundsShort& other) const {
|
||||
for (int axis = 0; axis < 3; ++axis) {
|
||||
if (b[0][axis] > other.b[1][axis]
|
||||
|| other.b[0][axis] > b[1][axis]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(idBoundsShort) == 12, "idBoundsShort ABI changed");
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "frustum.h"
|
||||
|
||||
void idFrustum::ToPoints(idVec3 points[8]) const {
|
||||
const idVec3 nearCenter = origin + axis[0] * dNear;
|
||||
const idVec3 nearLeft = axis[1] * (dLeft * dNear * invFar);
|
||||
const idVec3 nearUp = axis[2] * (dUp * dNear * invFar);
|
||||
points[0] = nearCenter + nearLeft + nearUp;
|
||||
points[1] = nearCenter - nearLeft + nearUp;
|
||||
points[2] = nearCenter - nearLeft - nearUp;
|
||||
points[3] = nearCenter + nearLeft - nearUp;
|
||||
|
||||
const idVec3 farCenter = origin + axis[0] * dFar;
|
||||
const idVec3 farLeft = axis[1] * dLeft;
|
||||
const idVec3 farUp = axis[2] * dUp;
|
||||
points[4] = farCenter + farLeft + farUp;
|
||||
points[5] = farCenter - farLeft + farUp;
|
||||
points[6] = farCenter - farLeft - farUp;
|
||||
points[7] = farCenter + farLeft - farUp;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "idlib/precompiled.h"
|
||||
|
||||
class idFrustum {
|
||||
public:
|
||||
idFrustum()
|
||||
: origin(vec3_origin), axis(mat3_identity), dNear(0.0f), dFar(0.0f),
|
||||
dLeft(0.0f), dUp(0.0f), invFar(0.0f) {
|
||||
}
|
||||
|
||||
idFrustum(const idVec3& newOrigin, const idMat3& newAxis,
|
||||
const float nearDistance, const float farDistance,
|
||||
const float farLeft, const float farUp)
|
||||
: origin(newOrigin), axis(newAxis), dNear(nearDistance),
|
||||
dFar(farDistance), dLeft(farLeft), dUp(farUp),
|
||||
invFar(farDistance != 0.0f ? 1.0f / farDistance : 0.0f) {
|
||||
}
|
||||
|
||||
void ToPoints(idVec3 points[8]) const;
|
||||
|
||||
private:
|
||||
idVec3 origin;
|
||||
idMat3 axis;
|
||||
float dNear;
|
||||
float dFar;
|
||||
float dLeft;
|
||||
float dUp;
|
||||
float invFar;
|
||||
};
|
||||
|
||||
static_assert(sizeof(idFrustum) == 68, "Recovered idFrustum ABI changed");
|
||||
@@ -0,0 +1,190 @@
|
||||
#include "color.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
const idColor idColor::colorBlack(0.00f, 0.00f, 0.00f, 1.00f);
|
||||
const idColor idColor::colorWhite(1.00f, 1.00f, 1.00f, 1.00f);
|
||||
const idColor idColor::colorRed(1.00f, 0.00f, 0.00f, 1.00f);
|
||||
const idColor idColor::colorGreen(0.00f, 1.00f, 0.00f, 1.00f);
|
||||
const idColor idColor::colorBlue(0.00f, 0.00f, 1.00f, 1.00f);
|
||||
const idColor idColor::colorYellow(1.00f, 1.00f, 0.00f, 1.00f);
|
||||
const idColor idColor::colorMagenta(1.00f, 0.00f, 1.00f, 1.00f);
|
||||
const idColor idColor::colorCyan(0.00f, 1.00f, 1.00f, 1.00f);
|
||||
const idColor idColor::colorOrange(1.00f, 0.50f, 0.00f, 1.00f);
|
||||
const idColor idColor::colorPurple(0.60f, 0.00f, 0.60f, 1.00f);
|
||||
const idColor idColor::colorPink(0.73f, 0.40f, 0.48f, 1.00f);
|
||||
const idColor idColor::colorBrown(0.40f, 0.35f, 0.08f, 1.00f);
|
||||
const idColor idColor::colorLtGrey(0.75f, 0.75f, 0.75f, 1.00f);
|
||||
const idColor idColor::colorMdGrey(0.50f, 0.50f, 0.50f, 1.00f);
|
||||
const idColor idColor::colorDkGrey(0.25f, 0.25f, 0.25f, 1.00f);
|
||||
const idColor idColor::colorDefault = idColor::colorWhite;
|
||||
|
||||
// These axis assignments look unusual but are explicit in the recovered
|
||||
// dynamic initializers: X=blue, Y=red, Z=green.
|
||||
const idColor idColor::colorXAxis = idColor::colorBlue;
|
||||
const idColor idColor::colorYAxis = idColor::colorRed;
|
||||
const idColor idColor::colorZAxis = idColor::colorGreen;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kStringBufferSize = 4096;
|
||||
|
||||
std::uint8_t FloatToByte(const float value) {
|
||||
const int integer = static_cast<int>(value * 255.0f);
|
||||
return static_cast<std::uint8_t>(std::max(0, std::min(255, integer)));
|
||||
}
|
||||
|
||||
void SkipWhitespace(const char*& cursor) {
|
||||
while (*cursor != '\0'
|
||||
&& std::isspace(static_cast<unsigned char>(*cursor)) != 0) {
|
||||
++cursor;
|
||||
}
|
||||
}
|
||||
|
||||
bool ParseFloat(const char*& cursor, float& value) {
|
||||
SkipWhitespace(cursor);
|
||||
char* end = nullptr;
|
||||
value = std::strtof(cursor, &end);
|
||||
if (end == cursor) {
|
||||
return false;
|
||||
}
|
||||
cursor = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AppendFloat(
|
||||
char* destination,
|
||||
const int destinationSize,
|
||||
int& length,
|
||||
const float value,
|
||||
const int precision,
|
||||
const bool addLeadingSpace
|
||||
) {
|
||||
char number[64];
|
||||
const int safePrecision = std::max(0, std::min(9, precision));
|
||||
std::snprintf(number, sizeof(number), "%.*f", safePrecision, value);
|
||||
|
||||
if (safePrecision > 0) {
|
||||
int numberLength = static_cast<int>(std::strlen(number));
|
||||
while (numberLength > 0 && number[numberLength - 1] == '0') {
|
||||
number[--numberLength] = '\0';
|
||||
}
|
||||
if (numberLength > 0 && number[numberLength - 1] == '.') {
|
||||
number[--numberLength] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
const int written = std::snprintf(
|
||||
destination + length,
|
||||
static_cast<std::size_t>(destinationSize - length),
|
||||
addLeadingSpace ? " %s" : "%s",
|
||||
number
|
||||
);
|
||||
if (written > 0) {
|
||||
length = std::min(destinationSize - 1, length + written);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const char* idColor::ToString(const int precision, const bool parens) const {
|
||||
static thread_local int bufferIndex = 0;
|
||||
static thread_local char buffers[4][kStringBufferSize];
|
||||
|
||||
char values[kStringBufferSize];
|
||||
values[0] = '\0';
|
||||
int length = 0;
|
||||
AppendFloat(values, kStringBufferSize, length, r, precision, false);
|
||||
AppendFloat(values, kStringBufferSize, length, g, precision, true);
|
||||
AppendFloat(values, kStringBufferSize, length, b, precision, true);
|
||||
AppendFloat(values, kStringBufferSize, length, a, precision, true);
|
||||
|
||||
char* const result = buffers[bufferIndex];
|
||||
bufferIndex = (bufferIndex + 1) & 3;
|
||||
if (parens) {
|
||||
std::snprintf(result, kStringBufferSize, "( %s )", values);
|
||||
} else {
|
||||
std::snprintf(result, kStringBufferSize, "%s", values);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void idColor::Lerp(
|
||||
const idColor& from,
|
||||
const idColor& to,
|
||||
const float lerp
|
||||
) {
|
||||
if (lerp <= 0.0f) {
|
||||
*this = from;
|
||||
} else if (lerp >= 1.0f) {
|
||||
*this = to;
|
||||
} else {
|
||||
r = from.r + (to.r - from.r) * lerp;
|
||||
g = from.g + (to.g - from.g) * lerp;
|
||||
b = from.b + (to.b - from.b) * lerp;
|
||||
a = from.a + (to.a - from.a) * lerp;
|
||||
}
|
||||
}
|
||||
|
||||
std::uint32_t idColor::PackColor(const idVec4& color) {
|
||||
const std::uint32_t red = FloatToByte(color.x);
|
||||
const std::uint32_t green = FloatToByte(color.y);
|
||||
const std::uint32_t blue = FloatToByte(color.z);
|
||||
const std::uint32_t alpha = FloatToByte(color.w);
|
||||
return red | (green << 8) | (blue << 16) | (alpha << 24);
|
||||
}
|
||||
|
||||
void idColor::UnpackColor(
|
||||
const std::uint32_t color,
|
||||
idVec4& unpackedColor
|
||||
) {
|
||||
constexpr float kByteToFloat = 1.0f / 255.0f;
|
||||
unpackedColor.Set(
|
||||
static_cast<float>((color >> 0) & 255) * kByteToFloat,
|
||||
static_cast<float>((color >> 8) & 255) * kByteToFloat,
|
||||
static_cast<float>((color >> 16) & 255) * kByteToFloat,
|
||||
static_cast<float>((color >> 24) & 255) * kByteToFloat
|
||||
);
|
||||
}
|
||||
|
||||
bool idColor::SetFromString(const char* str, const bool parseParens) {
|
||||
if (str == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* cursor = str;
|
||||
SkipWhitespace(cursor);
|
||||
if (parseParens) {
|
||||
if (*cursor != '(') {
|
||||
return false;
|
||||
}
|
||||
++cursor;
|
||||
}
|
||||
|
||||
idColor parsed;
|
||||
if (!ParseFloat(cursor, parsed.r)
|
||||
|| !ParseFloat(cursor, parsed.g)
|
||||
|| !ParseFloat(cursor, parsed.b)
|
||||
|| !ParseFloat(cursor, parsed.a)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SkipWhitespace(cursor);
|
||||
if (parseParens) {
|
||||
if (*cursor != ')') {
|
||||
return false;
|
||||
}
|
||||
++cursor;
|
||||
SkipWhitespace(cursor);
|
||||
}
|
||||
if (*cursor != '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
*this = parsed;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "math/vector.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// tungsten.exe.h type 12194.
|
||||
class idColor {
|
||||
public:
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
float a;
|
||||
|
||||
idColor() = default;
|
||||
|
||||
idColor(
|
||||
const float red,
|
||||
const float green,
|
||||
const float blue,
|
||||
const float alpha = 1.0f
|
||||
)
|
||||
: r(red)
|
||||
, g(green)
|
||||
, b(blue)
|
||||
, a(alpha) {
|
||||
}
|
||||
|
||||
const char* ToString(int precision, bool parens) const;
|
||||
void Lerp(const idColor& from, const idColor& to, float lerp);
|
||||
|
||||
static std::uint32_t PackColor(const idVec4& color);
|
||||
static void UnpackColor(std::uint32_t color, idVec4& unpackedColor);
|
||||
|
||||
bool SetFromString(const char* str, bool parseParens);
|
||||
|
||||
static const idColor colorBlack;
|
||||
static const idColor colorWhite;
|
||||
static const idColor colorRed;
|
||||
static const idColor colorGreen;
|
||||
static const idColor colorBlue;
|
||||
static const idColor colorYellow;
|
||||
static const idColor colorMagenta;
|
||||
static const idColor colorCyan;
|
||||
static const idColor colorOrange;
|
||||
static const idColor colorPurple;
|
||||
static const idColor colorPink;
|
||||
static const idColor colorBrown;
|
||||
static const idColor colorLtGrey;
|
||||
static const idColor colorMdGrey;
|
||||
static const idColor colorDkGrey;
|
||||
static const idColor colorDefault;
|
||||
static const idColor colorXAxis;
|
||||
static const idColor colorYAxis;
|
||||
static const idColor colorZAxis;
|
||||
};
|
||||
|
||||
static_assert(sizeof(idColor) == 16, "Recovered idColor layout changed");
|
||||
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
template<typename type>
|
||||
class idAutoPtr {
|
||||
public:
|
||||
explicit idAutoPtr(type* pointer = nullptr)
|
||||
: Pointee(pointer) {
|
||||
}
|
||||
|
||||
~idAutoPtr() {
|
||||
delete Pointee;
|
||||
}
|
||||
|
||||
idAutoPtr(idAutoPtr&& other) noexcept
|
||||
: Pointee(other.Release()) {
|
||||
}
|
||||
|
||||
idAutoPtr& operator=(idAutoPtr&& other) noexcept {
|
||||
if (this != &other) {
|
||||
Reset(other.Release());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
idAutoPtr(const idAutoPtr&) = delete;
|
||||
idAutoPtr& operator=(const idAutoPtr&) = delete;
|
||||
|
||||
type* Get() const { return Pointee; }
|
||||
type* operator->() const { return Pointee; }
|
||||
type& operator*() const { return *Pointee; }
|
||||
explicit operator bool() const { return Pointee != nullptr; }
|
||||
|
||||
type* Release() {
|
||||
type* const result = Pointee;
|
||||
Pointee = nullptr;
|
||||
return result;
|
||||
}
|
||||
|
||||
void Reset(type* pointer = nullptr) {
|
||||
if (Pointee != pointer) {
|
||||
delete Pointee;
|
||||
Pointee = pointer;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
type* Pointee;
|
||||
};
|
||||
|
||||
template<typename type>
|
||||
class idAutoPtr_Array {
|
||||
public:
|
||||
explicit idAutoPtr_Array(type* pointer = nullptr)
|
||||
: Pointee(pointer) {
|
||||
}
|
||||
|
||||
virtual ~idAutoPtr_Array() {
|
||||
std::free(Pointee);
|
||||
}
|
||||
|
||||
idAutoPtr_Array(const idAutoPtr_Array&) = delete;
|
||||
idAutoPtr_Array& operator=(const idAutoPtr_Array&) = delete;
|
||||
|
||||
type* Get() const { return Pointee; }
|
||||
type& operator[](const int index) const { return Pointee[index]; }
|
||||
|
||||
type* Release() {
|
||||
type* const result = Pointee;
|
||||
Pointee = nullptr;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
type* Pointee;
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idAutoPtr<int>) == 4, "Recovered idAutoPtr ABI changed");
|
||||
static_assert(sizeof(idAutoPtr_Array<int>) == 8,
|
||||
"Recovered idAutoPtr_Array ABI changed");
|
||||
#endif
|
||||
@@ -0,0 +1,5 @@
|
||||
#include "binaryheap.h"
|
||||
|
||||
// The recovered BinaryHeap.cpp contains only the testBinaryHeap console
|
||||
// command. The reusable implementation was inline in BinaryHeap.h; its PC
|
||||
// regression coverage lives in source/tests/idlib_containers_test.cpp.
|
||||
@@ -0,0 +1,188 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
template<typename nodeType, typename priorityType, priorityType LOWEST_VALUE>
|
||||
class idBinaryHeap {
|
||||
public:
|
||||
struct idHeapNode {
|
||||
nodeType node;
|
||||
priorityType priority;
|
||||
};
|
||||
|
||||
explicit idBinaryHeap(const int initialSize_ = 16)
|
||||
: nodes(nullptr), curSize(0), initialSize(std::max(1, initialSize_)),
|
||||
numNodes(0), ordered(true), externalBuffer(false) {
|
||||
Allocate(initialSize);
|
||||
}
|
||||
|
||||
idBinaryHeap(idHeapNode* const buffer, const int bufferSize)
|
||||
: nodes(buffer), curSize(std::max(0, bufferSize - 1)),
|
||||
initialSize(std::max(0, bufferSize - 1)), numNodes(0),
|
||||
ordered(true), externalBuffer(true) {
|
||||
if (nodes != nullptr && bufferSize > 0) {
|
||||
nodes[0].priority = LOWEST_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
~idBinaryHeap() {
|
||||
if (!externalBuffer) {
|
||||
std::free(nodes);
|
||||
}
|
||||
}
|
||||
|
||||
idBinaryHeap(const idBinaryHeap&) = delete;
|
||||
idBinaryHeap& operator=(const idBinaryHeap&) = delete;
|
||||
|
||||
bool Insert(const nodeType& node, const priorityType& priority) {
|
||||
if (!ordered) {
|
||||
return InsertUnsorted(node, priority);
|
||||
}
|
||||
if (!EnsureCapacity()) {
|
||||
return false;
|
||||
}
|
||||
int hole = ++numNodes;
|
||||
while (hole > 1 && priority < nodes[hole / 2].priority) {
|
||||
nodes[hole] = nodes[hole / 2];
|
||||
hole /= 2;
|
||||
}
|
||||
nodes[hole].node = node;
|
||||
nodes[hole].priority = priority;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InsertUnsorted(const nodeType& node, const priorityType& priority) {
|
||||
if (!EnsureCapacity()) {
|
||||
return false;
|
||||
}
|
||||
++numNodes;
|
||||
nodes[numNodes].node = node;
|
||||
nodes[numNodes].priority = priority;
|
||||
if (numNodes > 1 && priority < nodes[numNodes / 2].priority) {
|
||||
ordered = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
nodeType GetMin() {
|
||||
SortHeap();
|
||||
return numNodes == 0 ? nodeType() : nodes[1].node;
|
||||
}
|
||||
|
||||
priorityType GetMinPriority() {
|
||||
SortHeap();
|
||||
return numNodes == 0 ? LOWEST_VALUE : nodes[1].priority;
|
||||
}
|
||||
|
||||
nodeType RemoveMin() {
|
||||
SortHeap();
|
||||
if (numNodes == 0) {
|
||||
return nodeType();
|
||||
}
|
||||
const nodeType result = nodes[1].node;
|
||||
nodes[1] = nodes[numNodes--];
|
||||
if (numNodes > 0) {
|
||||
PercolateDown(1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void SortHeap() {
|
||||
if (ordered || numNodes < 2) {
|
||||
ordered = true;
|
||||
return;
|
||||
}
|
||||
for (int index = numNodes / 2; index > 0; --index) {
|
||||
PercolateDown(index);
|
||||
}
|
||||
ordered = true;
|
||||
}
|
||||
|
||||
void MakeEmpty() {
|
||||
if (!externalBuffer && curSize != initialSize) {
|
||||
std::free(nodes);
|
||||
nodes = nullptr;
|
||||
curSize = 0;
|
||||
Allocate(initialSize);
|
||||
}
|
||||
numNodes = 0;
|
||||
ordered = true;
|
||||
if (nodes != nullptr) {
|
||||
nodes[0].priority = LOWEST_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
int Num() const { return numNodes; }
|
||||
bool IsEmpty() const { return numNodes == 0; }
|
||||
|
||||
private:
|
||||
idHeapNode* nodes;
|
||||
int curSize;
|
||||
int initialSize;
|
||||
int numNodes;
|
||||
bool ordered;
|
||||
bool externalBuffer;
|
||||
|
||||
bool Allocate(const int size) {
|
||||
idHeapNode* const storage = static_cast<idHeapNode*>(
|
||||
std::malloc(sizeof(idHeapNode) * static_cast<std::size_t>(size + 1))
|
||||
);
|
||||
if (storage == nullptr) {
|
||||
return false;
|
||||
}
|
||||
nodes = storage;
|
||||
curSize = size;
|
||||
nodes[0].priority = LOWEST_VALUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Resize(const int newSize) {
|
||||
if (externalBuffer || newSize <= curSize) {
|
||||
return false;
|
||||
}
|
||||
idHeapNode* const replacement = static_cast<idHeapNode*>(
|
||||
std::malloc(sizeof(idHeapNode) * static_cast<std::size_t>(newSize + 1))
|
||||
);
|
||||
if (replacement == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(replacement, nodes,
|
||||
sizeof(idHeapNode) * static_cast<std::size_t>(numNodes + 1));
|
||||
std::free(nodes);
|
||||
nodes = replacement;
|
||||
curSize = newSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EnsureCapacity() {
|
||||
if (numNodes < curSize) {
|
||||
return true;
|
||||
}
|
||||
return Resize(std::max(1, curSize * 2));
|
||||
}
|
||||
|
||||
void PercolateDown(int hole) {
|
||||
const idHeapNode value = nodes[hole];
|
||||
while (hole * 2 <= numNodes) {
|
||||
int child = hole * 2;
|
||||
if (child != numNodes
|
||||
&& nodes[child + 1].priority < nodes[child].priority) {
|
||||
++child;
|
||||
}
|
||||
if (!(nodes[child].priority < value.priority)) {
|
||||
break;
|
||||
}
|
||||
nodes[hole] = nodes[child];
|
||||
hole = child;
|
||||
}
|
||||
nodes[hole] = value;
|
||||
}
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idBinaryHeap<int, int, (-2147483647 - 1)>) == 20,
|
||||
"Recovered idBinaryHeap ABI changed");
|
||||
#endif
|
||||
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
// Recovered from shared/idlib/containers/BitArray.h. The field order is
|
||||
// intentionally kept identical to the 32-bit tungsten type.
|
||||
class idBitArray {
|
||||
public:
|
||||
explicit idBitArray(const std::int16_t tag = 0)
|
||||
: buffer(nullptr), bits(0), memTag(tag), ownsBuffer(false) {
|
||||
}
|
||||
|
||||
idBitArray(unsigned char* storage, const unsigned int numBits,
|
||||
const std::int16_t tag = 0)
|
||||
: buffer(storage), bits(numBits), memTag(tag), ownsBuffer(false) {
|
||||
Clear();
|
||||
}
|
||||
|
||||
~idBitArray() {
|
||||
Free();
|
||||
}
|
||||
|
||||
idBitArray(const idBitArray&) = delete;
|
||||
idBitArray& operator=(const idBitArray&) = delete;
|
||||
|
||||
bool Alloc(const unsigned int numBits) {
|
||||
Free();
|
||||
bits = numBits;
|
||||
ownsBuffer = true;
|
||||
const std::size_t bytes = ByteCount();
|
||||
if (bytes == 0) {
|
||||
return true;
|
||||
}
|
||||
buffer = static_cast<unsigned char*>(std::calloc(bytes, 1));
|
||||
if (buffer == nullptr) {
|
||||
bits = 0;
|
||||
ownsBuffer = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
if (buffer != nullptr) {
|
||||
std::memset(buffer, 0, ByteCount());
|
||||
}
|
||||
}
|
||||
|
||||
void Set(const unsigned int bitNum) {
|
||||
if (bitNum < bits && buffer != nullptr) {
|
||||
buffer[bitNum >> 3] |= static_cast<unsigned char>(1u << (bitNum & 7));
|
||||
}
|
||||
}
|
||||
|
||||
void Clear(const unsigned int bitNum) {
|
||||
if (bitNum < bits && buffer != nullptr) {
|
||||
buffer[bitNum >> 3] &= static_cast<unsigned char>(~(1u << (bitNum & 7)));
|
||||
}
|
||||
}
|
||||
|
||||
void Set(const unsigned int bitNum, const bool value) {
|
||||
if (value) {
|
||||
Set(bitNum);
|
||||
} else {
|
||||
Clear(bitNum);
|
||||
}
|
||||
}
|
||||
|
||||
bool Get(const unsigned int bitNum) const {
|
||||
return bitNum < bits && buffer != nullptr
|
||||
&& (buffer[bitNum >> 3] & (1u << (bitNum & 7))) != 0;
|
||||
}
|
||||
|
||||
unsigned int Num() const {
|
||||
return bits;
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned char* buffer;
|
||||
unsigned int bits;
|
||||
std::int16_t memTag;
|
||||
bool ownsBuffer;
|
||||
|
||||
std::size_t ByteCount() const {
|
||||
return static_cast<std::size_t>((bits + 7u) >> 3);
|
||||
}
|
||||
|
||||
void Free() {
|
||||
if (buffer != nullptr && ownsBuffer) {
|
||||
std::free(buffer);
|
||||
}
|
||||
buffer = nullptr;
|
||||
bits = 0;
|
||||
ownsBuffer = false;
|
||||
}
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idBitArray) == 12, "Recovered idBitArray ABI changed");
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
// Tungsten's containers/BTree.cpp contains only the testBinaryTree console
|
||||
// command. The reusable idBTree implementation is header-only and is supplied
|
||||
// by the compiled Doom 3 BFG idlib/containers/BTree.h baseline.
|
||||
@@ -0,0 +1,4 @@
|
||||
// Tungsten's containers/List.cpp contains diagnostic console commands for the
|
||||
// header-only idList and idArray implementations. The PC runtime templates are
|
||||
// supplied by the compiled Doom 3 BFG idlib/containers/List.h baseline; the
|
||||
// recovery suite exercises container behavior without registering engine CVars.
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
|
||||
template<class T>
|
||||
class idRecoveredList {
|
||||
public:
|
||||
explicit idRecoveredList(const int initialGranularity = 16)
|
||||
: list(nullptr), num(0), size(0),
|
||||
granularity(static_cast<std::int16_t>(initialGranularity)),
|
||||
memTag(0), listStatic(0) {
|
||||
}
|
||||
|
||||
~idRecoveredList() {
|
||||
Clear(true);
|
||||
}
|
||||
|
||||
idRecoveredList(const idRecoveredList&) = delete;
|
||||
idRecoveredList& operator=(const idRecoveredList&) = delete;
|
||||
|
||||
bool Reserve(const int capacity) {
|
||||
if (capacity <= size) return true;
|
||||
const int step = granularity > 0 ? granularity : 16;
|
||||
const int newSize = ((capacity + step - 1) / step) * step;
|
||||
T* const replacement = static_cast<T*>(
|
||||
std::malloc(sizeof(T) * static_cast<std::size_t>(newSize)));
|
||||
if (replacement == nullptr) return false;
|
||||
if (list != nullptr && num > 0) {
|
||||
std::memcpy(replacement, list,
|
||||
sizeof(T) * static_cast<std::size_t>(num));
|
||||
}
|
||||
std::free(list);
|
||||
list = replacement;
|
||||
size = newSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
T* Alloc() {
|
||||
if (!Reserve(num + 1)) return nullptr;
|
||||
T* const result = list + num++;
|
||||
std::memset(result, 0, sizeof(T));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Append(const T& value) {
|
||||
T* const destination = Alloc();
|
||||
if (destination == nullptr) return false;
|
||||
*destination = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Clear(const bool freeMemory = false) {
|
||||
num = 0;
|
||||
if (freeMemory) {
|
||||
std::free(list);
|
||||
list = nullptr;
|
||||
size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int Num() const { return num; }
|
||||
int Capacity() const { return size; }
|
||||
T* Ptr() { return list; }
|
||||
const T* Ptr() const { return list; }
|
||||
T& operator[](const int index) { return list[index]; }
|
||||
const T& operator[](const int index) const { return list[index]; }
|
||||
|
||||
private:
|
||||
T* list;
|
||||
int num;
|
||||
int size;
|
||||
std::int16_t granularity;
|
||||
std::uint8_t memTag;
|
||||
std::uint8_t listStatic;
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idRecoveredList<int>) == 16,
|
||||
"Recovered list ABI changed");
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
template<typename type>
|
||||
class idSearch {
|
||||
public:
|
||||
virtual ~idSearch() = default;
|
||||
virtual int Search(const type* base, unsigned int num,
|
||||
const type& value) const = 0;
|
||||
virtual int Search_FirstGreater(const type* base, int num,
|
||||
const type& value) const = 0;
|
||||
virtual int Search_FirstGreaterEqual(const type* base, int num,
|
||||
const type& value) const = 0;
|
||||
virtual int Search_LastLess(const type* base, int num,
|
||||
const type& value) const = 0;
|
||||
virtual int Search_LastLessEqual(const type* base, int num,
|
||||
const type& value) const = 0;
|
||||
};
|
||||
|
||||
template<typename type, typename derived>
|
||||
class idSearch_Binary : public idSearch<type> {
|
||||
public:
|
||||
int Search(const type* base, const unsigned int num,
|
||||
const type& value) const override {
|
||||
if (base == nullptr || num == 0) {
|
||||
return -1;
|
||||
}
|
||||
const int index = Search_LastLessEqual(base, static_cast<int>(num), value);
|
||||
return Compare(base[index], value) == 0 ? index : -1;
|
||||
}
|
||||
|
||||
int Search_FirstGreater(const type* base, const int num,
|
||||
const type& value) const override {
|
||||
int first = 0;
|
||||
int count = num < 0 ? 0 : num;
|
||||
while (count > 0) {
|
||||
const int step = count / 2;
|
||||
const int middle = first + step;
|
||||
if (Compare(base[middle], value) <= 0) {
|
||||
first = middle + 1;
|
||||
count -= step + 1;
|
||||
} else {
|
||||
count = step;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
int Search_FirstGreaterEqual(const type* base, const int num,
|
||||
const type& value) const override {
|
||||
int first = 0;
|
||||
int count = num < 0 ? 0 : num;
|
||||
while (count > 0) {
|
||||
const int step = count / 2;
|
||||
const int middle = first + step;
|
||||
if (Compare(base[middle], value) < 0) {
|
||||
first = middle + 1;
|
||||
count -= step + 1;
|
||||
} else {
|
||||
count = step;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
int Search_LastLess(const type* base, const int num,
|
||||
const type& value) const override {
|
||||
const int result = Search_FirstGreaterEqual(base, num, value) - 1;
|
||||
return result < 0 ? 0 : result;
|
||||
}
|
||||
|
||||
int Search_LastLessEqual(const type* base, const int num,
|
||||
const type& value) const override {
|
||||
const int result = Search_FirstGreater(base, num, value) - 1;
|
||||
return result < 0 ? 0 : result;
|
||||
}
|
||||
|
||||
private:
|
||||
int Compare(const type& left, const type& right) const {
|
||||
return static_cast<const derived*>(this)->Compare(left, right);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename type>
|
||||
class idSearch_BinaryDefault
|
||||
: public idSearch_Binary<type, idSearch_BinaryDefault<type>> {
|
||||
public:
|
||||
int Compare(const type& left, const type& right) const {
|
||||
if (left < right) {
|
||||
return -1;
|
||||
}
|
||||
if (right < left) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename type>
|
||||
using idSearch_DefaultCompare = idSearch_BinaryDefault<type>;
|
||||
@@ -0,0 +1,101 @@
|
||||
#include "autocomplete.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
|
||||
namespace {
|
||||
int ComparePrefixInsensitive(const char* left, const char* right,
|
||||
const int count) {
|
||||
const unsigned char* safeLeft = reinterpret_cast<const unsigned char*>(
|
||||
left == nullptr ? "" : left
|
||||
);
|
||||
const unsigned char* safeRight = reinterpret_cast<const unsigned char*>(
|
||||
right == nullptr ? "" : right
|
||||
);
|
||||
for (int index = 0; index < count; ++index) {
|
||||
const int leftChar = std::tolower(safeLeft[index]);
|
||||
const int rightChar = std::tolower(safeRight[index]);
|
||||
if (leftChar != rightChar || safeLeft[index] == '\0'
|
||||
|| safeRight[index] == '\0') {
|
||||
return leftChar - rightChar;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
idAutoComplete::~idAutoComplete() {
|
||||
ClearSuggestions();
|
||||
}
|
||||
|
||||
void idAutoComplete::ClearSuggestions() {
|
||||
if (suggestions.list != nullptr) {
|
||||
for (int index = 0; index < suggestions.size; ++index) {
|
||||
suggestions.list[index].~idStr();
|
||||
}
|
||||
std::free(suggestions.list);
|
||||
}
|
||||
suggestions.list = nullptr;
|
||||
suggestions.num = 0;
|
||||
suggestions.size = 0;
|
||||
}
|
||||
|
||||
void idAutoComplete::AppendSuggestion(const idStr& suggestion) {
|
||||
if (suggestions.num == suggestions.size) {
|
||||
const int newSize = suggestions.size + suggestions.granularity;
|
||||
idStr* const replacement = static_cast<idStr*>(
|
||||
std::malloc(sizeof(idStr) * static_cast<std::size_t>(newSize))
|
||||
);
|
||||
if (replacement == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int index = 0; index < newSize; ++index) {
|
||||
new (&replacement[index]) idStr();
|
||||
}
|
||||
for (int index = 0; index < suggestions.num; ++index) {
|
||||
replacement[index] = suggestions.list[index];
|
||||
}
|
||||
if (suggestions.list != nullptr) {
|
||||
for (int index = 0; index < suggestions.size; ++index) {
|
||||
suggestions.list[index].~idStr();
|
||||
}
|
||||
std::free(suggestions.list);
|
||||
}
|
||||
suggestions.list = replacement;
|
||||
suggestions.size = newSize;
|
||||
}
|
||||
|
||||
suggestions.list[suggestions.num++] = suggestion;
|
||||
}
|
||||
|
||||
void idAutoComplete::Append(
|
||||
const idStr& suggestion,
|
||||
const int completingArg
|
||||
) {
|
||||
const int argumentIndex = completingArg < 0
|
||||
? args.Argc() - 1
|
||||
: completingArg;
|
||||
|
||||
const char* const partial = args.Argv(argumentIndex);
|
||||
const int partialLength = static_cast<int>(std::strlen(partial));
|
||||
if (partialLength > 0
|
||||
&& ComparePrefixInsensitive(suggestion.c_str(), partial, partialLength) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Argc() == 1 && argumentIndex == 0) {
|
||||
AppendSuggestion(suggestion);
|
||||
return;
|
||||
}
|
||||
|
||||
idStr completed;
|
||||
for (int index = 0; index < argumentIndex; ++index) {
|
||||
completed.Append(args.Argv(index));
|
||||
completed.Append(' ');
|
||||
}
|
||||
completed.Append(suggestion);
|
||||
AppendSuggestion(completed);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include "../text/cmdargs.h"
|
||||
#include "../text/str.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct idAutoCompleteStringList {
|
||||
idStr* list;
|
||||
int num;
|
||||
int size;
|
||||
std::int16_t granularity;
|
||||
std::uint8_t memTag;
|
||||
std::uint8_t listStatic;
|
||||
};
|
||||
|
||||
static_assert(
|
||||
sizeof(idAutoCompleteStringList) == 16,
|
||||
"Recovered idList<idStr, 5> layout changed"
|
||||
);
|
||||
|
||||
// tungsten.exe.h type 12218. idCmdArgs and idList retain their recovered
|
||||
// Win32 layouts through the active BFG-compatible core container layer.
|
||||
class idAutoComplete {
|
||||
public:
|
||||
idAutoComplete()
|
||||
: matchLength(0)
|
||||
, currentIndex(-1)
|
||||
, suggestions{nullptr, 0, 0, 16, 5, 0} {
|
||||
}
|
||||
|
||||
~idAutoComplete();
|
||||
|
||||
void Append(const idStr& suggestion, int completingArg = -1);
|
||||
|
||||
const char* GetArg(const int index) const {
|
||||
return args.Argv(index);
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
matchLength = 0;
|
||||
currentIndex = -1;
|
||||
args.Clear();
|
||||
ClearSuggestions();
|
||||
}
|
||||
|
||||
void SetArgs(const idCmdArgs& newArgs) {
|
||||
args = newArgs;
|
||||
}
|
||||
|
||||
int GetNumSuggestions() const {
|
||||
return suggestions.num;
|
||||
}
|
||||
|
||||
const idStr& GetSuggestion(const int index) const {
|
||||
return suggestions.list[index];
|
||||
}
|
||||
|
||||
private:
|
||||
int matchLength;
|
||||
int currentIndex;
|
||||
idCmdArgs args;
|
||||
idAutoCompleteStringList suggestions;
|
||||
|
||||
void AppendSuggestion(const idStr& suggestion);
|
||||
void ClearSuggestions();
|
||||
};
|
||||
|
||||
static_assert(sizeof(idAutoComplete) == 2332, "idAutoComplete ABI changed");
|
||||
@@ -0,0 +1,244 @@
|
||||
#include "file_metrics.h"
|
||||
|
||||
#include "idlib/sys/sys_networking.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
namespace {
|
||||
|
||||
idTCP metricsTCP;
|
||||
unsigned char* sendQueue = nullptr;
|
||||
int sendQueueSize = 0;
|
||||
int sendIndex = 0;
|
||||
int pendingIndex = 0;
|
||||
char metricsServer[256] = "127.0.0.1";
|
||||
unsigned short metricsPort = 8012;
|
||||
int initialRetryTime = 1000;
|
||||
int maximumRetryTime = 60000;
|
||||
int retryTime = 0;
|
||||
std::uint64_t timeoutWait = 0;
|
||||
std::mutex metricsMutex;
|
||||
|
||||
std::uint64_t Milliseconds() {
|
||||
using namespace std::chrono;
|
||||
return static_cast<std::uint64_t>(duration_cast<milliseconds>(
|
||||
steady_clock::now().time_since_epoch()).count());
|
||||
}
|
||||
|
||||
void PutUInt32LE(unsigned char* output, const unsigned int value) {
|
||||
output[0] = static_cast<unsigned char>(value);
|
||||
output[1] = static_cast<unsigned char>(value >> 8);
|
||||
output[2] = static_cast<unsigned char>(value >> 16);
|
||||
output[3] = static_cast<unsigned char>(value >> 24);
|
||||
}
|
||||
|
||||
int StringBytes(const char* text) {
|
||||
return static_cast<int>(std::strlen(text == nullptr ? "" : text)) + 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
idFile_Metrics::idFile_Metrics(const char* streamName)
|
||||
: uniqID(0), name(streamName == nullptr ? "" : streamName), bytesSent(0) {
|
||||
}
|
||||
|
||||
idFile_Metrics::~idFile_Metrics() {
|
||||
CloseMetricsStream(name.c_str());
|
||||
}
|
||||
|
||||
const char* idFile_Metrics::GetFullPath() const {
|
||||
const netadr_t address = metricsTCP.GetAddress();
|
||||
if (metricsTCP.IsOpen()) {
|
||||
fullpath.Format("metrics://%u.%u.%u.%u:%u/%s",
|
||||
static_cast<unsigned int>(address.ip[0]),
|
||||
static_cast<unsigned int>(address.ip[1]),
|
||||
static_cast<unsigned int>(address.ip[2]),
|
||||
static_cast<unsigned int>(address.ip[3]),
|
||||
static_cast<unsigned int>(address.port), name.c_str());
|
||||
} else {
|
||||
fullpath.Format("metrics://%s:%u/%s", metricsServer,
|
||||
static_cast<unsigned int>(metricsPort), name.c_str());
|
||||
}
|
||||
return fullpath.c_str();
|
||||
}
|
||||
|
||||
int idFile_Metrics::Read(void*, int) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int idFile_Metrics::Write(const void* buffer, const int len) {
|
||||
const int written = WriteInternal(name.c_str(), buffer, len);
|
||||
bytesSent += written;
|
||||
return written;
|
||||
}
|
||||
|
||||
int idFile_Metrics::Seek(long, fsOrigin_t) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
void idFile_Metrics::Flush() {
|
||||
std::lock_guard<std::mutex> lock(metricsMutex);
|
||||
bool queueTraffic = false;
|
||||
FlushBufferedWrites(queueTraffic);
|
||||
}
|
||||
|
||||
void idFile_Metrics::ForceFlush() {
|
||||
Flush();
|
||||
}
|
||||
|
||||
idFile_Metrics* idFile_Metrics::OpenMetricsStream(const char* streamName) {
|
||||
return new idFile_Metrics(streamName);
|
||||
}
|
||||
|
||||
void idFile_Metrics::CloseMetricsStream(const char* streamName) {
|
||||
std::lock_guard<std::mutex> lock(metricsMutex);
|
||||
if (!metricsTCP.IsOpen()) return;
|
||||
|
||||
bool queueTraffic = false;
|
||||
FlushBufferedWrites(queueTraffic);
|
||||
|
||||
static const char control[] = "CONTROL";
|
||||
static const char close[] = "CLOSE";
|
||||
const char* const safeName = streamName == nullptr ? "" : streamName;
|
||||
const int payloadSize = static_cast<int>(sizeof(control) + sizeof(close))
|
||||
+ StringBytes(safeName);
|
||||
unsigned char prefix[4] = {};
|
||||
PutUInt32LE(prefix, static_cast<unsigned int>(payloadSize));
|
||||
BufferedWriteInternal(queueTraffic, prefix, sizeof(prefix));
|
||||
BufferedWriteInternal(queueTraffic, control, sizeof(control));
|
||||
BufferedWriteInternal(queueTraffic, close, sizeof(close));
|
||||
BufferedWriteInternal(queueTraffic, safeName, StringBytes(safeName));
|
||||
if (!queueTraffic) FlushBufferedWrites(queueTraffic);
|
||||
}
|
||||
|
||||
void idFile_Metrics::ConfigureServer(const char* host, const unsigned short port,
|
||||
const int initialRetryMilliseconds,
|
||||
const int maximumRetryMilliseconds) {
|
||||
std::lock_guard<std::mutex> lock(metricsMutex);
|
||||
metricsTCP.Close();
|
||||
const char* const safeHost = host == nullptr || *host == '\0'
|
||||
? "127.0.0.1" : host;
|
||||
std::strncpy(metricsServer, safeHost, sizeof(metricsServer) - 1);
|
||||
metricsServer[sizeof(metricsServer) - 1] = '\0';
|
||||
metricsPort = port;
|
||||
initialRetryTime = std::max(1, initialRetryMilliseconds);
|
||||
maximumRetryTime = std::max(initialRetryTime, maximumRetryMilliseconds);
|
||||
retryTime = 0;
|
||||
timeoutWait = 0;
|
||||
sendIndex = pendingIndex = 0;
|
||||
}
|
||||
|
||||
void idFile_Metrics::ShutdownTransport() {
|
||||
std::lock_guard<std::mutex> lock(metricsMutex);
|
||||
metricsTCP.Close();
|
||||
std::free(sendQueue);
|
||||
sendQueue = nullptr;
|
||||
sendQueueSize = sendIndex = pendingIndex = 0;
|
||||
retryTime = 0;
|
||||
timeoutWait = 0;
|
||||
}
|
||||
|
||||
bool idFile_Metrics::EnsureConnection() {
|
||||
if (metricsTCP.IsOpen()) return true;
|
||||
if (Milliseconds() < timeoutWait || metricsPort == 0) return false;
|
||||
|
||||
// A blocking connect gives the PC port a definite connected state. All
|
||||
// subsequent writes retain the non-blocking short-write queue semantics.
|
||||
if (metricsTCP.Connect(metricsServer, metricsPort, false, true, true)) {
|
||||
retryTime = 0;
|
||||
timeoutWait = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
retryTime = retryTime == 0 ? initialRetryTime
|
||||
: std::min(maximumRetryTime, retryTime * 2);
|
||||
timeoutWait = Milliseconds() + static_cast<std::uint64_t>(retryTime);
|
||||
return false;
|
||||
}
|
||||
|
||||
void idFile_Metrics::WriteToQueue(const void* buffer, const int len) {
|
||||
if (buffer == nullptr || len <= 0) return;
|
||||
const int retained = pendingIndex - sendIndex;
|
||||
const int required = retained + len;
|
||||
if (required > sendQueueSize) {
|
||||
int newSize = std::max(1024, sendQueueSize);
|
||||
while (newSize < required) newSize *= 2;
|
||||
unsigned char* const replacement = static_cast<unsigned char*>(
|
||||
std::malloc(static_cast<std::size_t>(newSize)));
|
||||
if (replacement == nullptr) return;
|
||||
if (retained > 0) {
|
||||
std::memcpy(replacement, sendQueue + sendIndex,
|
||||
static_cast<std::size_t>(retained));
|
||||
}
|
||||
std::free(sendQueue);
|
||||
sendQueue = replacement;
|
||||
sendQueueSize = newSize;
|
||||
sendIndex = 0;
|
||||
pendingIndex = retained;
|
||||
} else if (sendIndex > 0 && pendingIndex + len > sendQueueSize) {
|
||||
if (retained > 0) {
|
||||
std::memmove(sendQueue, sendQueue + sendIndex,
|
||||
static_cast<std::size_t>(retained));
|
||||
}
|
||||
sendIndex = 0;
|
||||
pendingIndex = retained;
|
||||
}
|
||||
std::memcpy(sendQueue + pendingIndex, buffer, static_cast<std::size_t>(len));
|
||||
pendingIndex += len;
|
||||
}
|
||||
|
||||
void idFile_Metrics::BufferedWriteInternal(bool& queueTraffic,
|
||||
const void* buffer, const int len) {
|
||||
if (buffer == nullptr || len <= 0) return;
|
||||
if (queueTraffic) {
|
||||
WriteToQueue(buffer, len);
|
||||
return;
|
||||
}
|
||||
const int written = metricsTCP.Write(buffer, len);
|
||||
const int validWritten = std::max(0, written);
|
||||
if (written != len) {
|
||||
queueTraffic = true;
|
||||
WriteToQueue(static_cast<const unsigned char*>(buffer) + validWritten,
|
||||
len - validWritten);
|
||||
}
|
||||
}
|
||||
|
||||
void idFile_Metrics::FlushBufferedWrites(bool& queueTraffic) {
|
||||
if (!metricsTCP.IsOpen() || sendIndex >= pendingIndex) {
|
||||
if (sendIndex >= pendingIndex) sendIndex = pendingIndex = 0;
|
||||
return;
|
||||
}
|
||||
const int available = pendingIndex - sendIndex;
|
||||
const int written = metricsTCP.Write(sendQueue + sendIndex, available);
|
||||
if (written > 0) sendIndex += written;
|
||||
if (written < available) queueTraffic = true;
|
||||
if (sendIndex >= pendingIndex) sendIndex = pendingIndex = 0;
|
||||
}
|
||||
|
||||
void idFile_Metrics::WriteFrame(bool& queueTraffic, const char* streamName,
|
||||
const void* buffer, const int len) {
|
||||
const char* const safeName = streamName == nullptr ? "" : streamName;
|
||||
const int nameBytes = StringBytes(safeName);
|
||||
unsigned char prefix[4] = {};
|
||||
PutUInt32LE(prefix, static_cast<unsigned int>(nameBytes + len));
|
||||
BufferedWriteInternal(queueTraffic, prefix, sizeof(prefix));
|
||||
BufferedWriteInternal(queueTraffic, safeName, nameBytes);
|
||||
BufferedWriteInternal(queueTraffic, buffer, len);
|
||||
}
|
||||
|
||||
int idFile_Metrics::WriteInternal(const char* streamName,
|
||||
const void* buffer, const int len) {
|
||||
if (buffer == nullptr || len <= 0) return 0;
|
||||
std::lock_guard<std::mutex> lock(metricsMutex);
|
||||
if (!EnsureConnection()) return 0;
|
||||
|
||||
bool queueTraffic = false;
|
||||
FlushBufferedWrites(queueTraffic);
|
||||
WriteFrame(queueTraffic, streamName, buffer, len);
|
||||
return len;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
// The recovery target still uses Doom 3 BFG's portable idFile implementation
|
||||
// as its base file layer. idFile_Metrics retains the tungsten data layout and
|
||||
// supplies the additional network-backed stream behavior recovered from the
|
||||
// Xbox 360 executable.
|
||||
#include "idlib/precompiled.h"
|
||||
|
||||
// BFG emulates the C++11 keyword for its original compiler. Recovery sources
|
||||
// use the real C++14 keyword and the standard library.
|
||||
#ifdef nullptr
|
||||
#undef nullptr
|
||||
#endif
|
||||
|
||||
class idFile_Metrics : public idFile {
|
||||
public:
|
||||
explicit idFile_Metrics(const char* streamName);
|
||||
~idFile_Metrics() override;
|
||||
|
||||
const char* GetName() const override { return name.c_str(); }
|
||||
const char* GetFullPath() const override;
|
||||
int Read(void* buffer, int len) override;
|
||||
int Write(const void* buffer, int len) override;
|
||||
int Length() const override { return bytesSent; }
|
||||
int Tell() const override { return bytesSent; }
|
||||
int Seek(long offset, fsOrigin_t origin) override;
|
||||
void Flush() override;
|
||||
void ForceFlush() override;
|
||||
|
||||
static idFile_Metrics* OpenMetricsStream(const char* streamName);
|
||||
static void CloseMetricsStream(const char* streamName);
|
||||
|
||||
// PC recovery controls. The original values came from metrics_* CVars.
|
||||
static void ConfigureServer(const char* host, unsigned short port,
|
||||
int initialRetryMilliseconds = 1000,
|
||||
int maximumRetryMilliseconds = 60000);
|
||||
static void ShutdownTransport();
|
||||
|
||||
private:
|
||||
static int WriteInternal(const char* streamName, const void* buffer, int len);
|
||||
static void WriteToQueue(const void* buffer, int len);
|
||||
static void BufferedWriteInternal(bool& queueTraffic,
|
||||
const void* buffer, int len);
|
||||
static void FlushBufferedWrites(bool& queueTraffic);
|
||||
static bool EnsureConnection();
|
||||
static void WriteFrame(bool& queueTraffic, const char* streamName,
|
||||
const void* buffer, int len);
|
||||
|
||||
// idTech 5 added uniqID to idFile; BFG's portable base predates it. Keep
|
||||
// the field in the same derived-object position to preserve tungsten ABI.
|
||||
unsigned int uniqID;
|
||||
idStr name;
|
||||
mutable idStr fullpath;
|
||||
int bytesSent;
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idFile_Metrics) == 76,
|
||||
"Recovered idFile_Metrics ABI changed");
|
||||
#endif
|
||||
@@ -0,0 +1,279 @@
|
||||
#include "file_mtp.h"
|
||||
|
||||
#include "idlib/sys/sys_networking.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
idTCP mtpTCP;
|
||||
char mtpServer[256] = "10.4.20.180";
|
||||
unsigned short mtpPort = 2769;
|
||||
int mtpWriteSize = 1400;
|
||||
int mtpTimeout = 1000;
|
||||
std::mutex mtpMutex;
|
||||
|
||||
void PutUInt16BE(unsigned char* output, const unsigned int value) {
|
||||
output[0] = static_cast<unsigned char>(value >> 8);
|
||||
output[1] = static_cast<unsigned char>(value);
|
||||
}
|
||||
|
||||
void PutUInt32BE(unsigned char* output, const unsigned int value) {
|
||||
output[0] = static_cast<unsigned char>(value >> 24);
|
||||
output[1] = static_cast<unsigned char>(value >> 16);
|
||||
output[2] = static_cast<unsigned char>(value >> 8);
|
||||
output[3] = static_cast<unsigned char>(value);
|
||||
}
|
||||
|
||||
void PutUInt64BE(unsigned char* output, const std::uint64_t value) {
|
||||
PutUInt32BE(output, static_cast<unsigned int>(value >> 32));
|
||||
PutUInt32BE(output + 4, static_cast<unsigned int>(value));
|
||||
}
|
||||
|
||||
unsigned int GetUInt32BE(const unsigned char* input) {
|
||||
return (static_cast<unsigned int>(input[0]) << 24)
|
||||
| (static_cast<unsigned int>(input[1]) << 16)
|
||||
| (static_cast<unsigned int>(input[2]) << 8)
|
||||
| static_cast<unsigned int>(input[3]);
|
||||
}
|
||||
|
||||
std::uint64_t GetUInt64BE(const unsigned char* input) {
|
||||
return (static_cast<std::uint64_t>(GetUInt32BE(input)) << 32)
|
||||
| GetUInt32BE(input + 4);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
idFile_MTP::idFile_MTP()
|
||||
: uniqID(0), position(0), mode(MTP_FS_READ), length(0),
|
||||
timestamp(static_cast<unsigned int>(-1)) {
|
||||
}
|
||||
|
||||
idFile_MTP::~idFile_MTP() = default;
|
||||
|
||||
void idFile_MTP::ConfigureServer(const char* host, const unsigned short port,
|
||||
const int writeSize, const int timeoutMilliseconds) {
|
||||
std::lock_guard<std::mutex> lock(mtpMutex);
|
||||
mtpTCP.Close();
|
||||
const char* const safeHost = host == nullptr || *host == '\0'
|
||||
? "127.0.0.1" : host;
|
||||
std::strncpy(mtpServer, safeHost, sizeof(mtpServer) - 1);
|
||||
mtpServer[sizeof(mtpServer) - 1] = '\0';
|
||||
mtpPort = port;
|
||||
mtpWriteSize = std::max(1, writeSize);
|
||||
mtpTimeout = std::max(1, timeoutMilliseconds);
|
||||
}
|
||||
|
||||
void idFile_MTP::ShutdownTransport() {
|
||||
std::lock_guard<std::mutex> lock(mtpMutex);
|
||||
mtpTCP.Close();
|
||||
}
|
||||
|
||||
bool idFile_MTP::EnsureConnection() {
|
||||
return mtpTCP.IsOpen()
|
||||
|| mtpTCP.Connect(mtpServer, mtpPort, false, true, false);
|
||||
}
|
||||
|
||||
bool idFile_MTP::SendRequest(const std::uint64_t offset,
|
||||
const unsigned int requestLength, const operation_t operation,
|
||||
const char* filename) {
|
||||
if (!EnsureConnection()) return false;
|
||||
const char* const safeName = filename == nullptr ? "" : filename;
|
||||
const std::size_t filenameLength = std::min<std::size_t>(
|
||||
std::strlen(safeName), 0xFFFFu);
|
||||
unsigned char request[16] = {};
|
||||
PutUInt64BE(request, offset);
|
||||
PutUInt32BE(request + 8, requestLength);
|
||||
PutUInt16BE(request + 12, static_cast<unsigned int>(filenameLength));
|
||||
PutUInt16BE(request + 14, operation);
|
||||
if (mtpTCP.WriteBlocking(request, sizeof(request), mtpTimeout)
|
||||
!= sizeof(request)) {
|
||||
mtpTCP.Close();
|
||||
return false;
|
||||
}
|
||||
if (filenameLength > 0 && mtpTCP.WriteBlocking(safeName,
|
||||
static_cast<int>(filenameLength), mtpTimeout)
|
||||
!= static_cast<int>(filenameLength)) {
|
||||
mtpTCP.Close();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool idFile_MTP::Open(const char* filename, const mtpFileMode_t openMode) {
|
||||
const char* const safeName = filename == nullptr ? "" : filename;
|
||||
name = safeName;
|
||||
fullPath.Format("MTP:%s", safeName);
|
||||
mode = openMode;
|
||||
position = 0;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mtpMutex);
|
||||
if (!SendRequest(0, static_cast<unsigned int>(openMode), OP_OPEN,
|
||||
name.c_str())) return false;
|
||||
unsigned char stats[16] = {};
|
||||
if (mtpTCP.ReadBlocking(stats, sizeof(stats), mtpTimeout)
|
||||
!= sizeof(stats)) {
|
||||
mtpTCP.Close();
|
||||
return false;
|
||||
}
|
||||
length = GetUInt64BE(stats);
|
||||
timestamp = GetUInt32BE(stats + 8);
|
||||
if (timestamp == static_cast<unsigned int>(-1)) return false;
|
||||
if (mode == MTP_FS_APPEND) position = length;
|
||||
return true;
|
||||
}
|
||||
|
||||
int idFile_MTP::Read(void* buffer, const int len) {
|
||||
return ReadOfs(static_cast<std::int64_t>(position), buffer, len);
|
||||
}
|
||||
|
||||
int idFile_MTP::ReadOfs(const std::int64_t offset, void* buffer, const int len) {
|
||||
if (buffer == nullptr || len <= 0 || offset < 0
|
||||
|| (mode != MTP_FS_READ && mode != MTP_FS_READ_WRITE
|
||||
&& mode != MTP_FS_READ_NO_BUFFERING)) return 0;
|
||||
std::lock_guard<std::mutex> lock(mtpMutex);
|
||||
|
||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||
if (!SendRequest(static_cast<std::uint64_t>(offset),
|
||||
static_cast<unsigned int>(len), OP_READ, name.c_str())) {
|
||||
mtpTCP.Close();
|
||||
continue;
|
||||
}
|
||||
int total = 0;
|
||||
bool failed = false;
|
||||
while (total < len) {
|
||||
unsigned char sizeBytes[4] = {};
|
||||
if (mtpTCP.ReadBlocking(sizeBytes, sizeof(sizeBytes), mtpTimeout)
|
||||
!= sizeof(sizeBytes)) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
const unsigned int chunk = GetUInt32BE(sizeBytes);
|
||||
if (chunk == 0) break;
|
||||
if (chunk > static_cast<unsigned int>(len - total)
|
||||
|| mtpTCP.ReadBlocking(static_cast<unsigned char*>(buffer)
|
||||
+ total, static_cast<int>(chunk), mtpTimeout)
|
||||
!= static_cast<int>(chunk)) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
total += static_cast<int>(chunk);
|
||||
}
|
||||
if (!failed) {
|
||||
position = static_cast<std::uint64_t>(offset) + total;
|
||||
return total;
|
||||
}
|
||||
mtpTCP.Close();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int idFile_MTP::Write(const void* buffer, const int len) {
|
||||
return WriteOfs(static_cast<std::int64_t>(position), buffer, len);
|
||||
}
|
||||
|
||||
int idFile_MTP::WriteOfs(const std::int64_t offset, const void* buffer,
|
||||
const int len) {
|
||||
if (buffer == nullptr || len <= 0 || offset < 0
|
||||
|| (mode != MTP_FS_WRITE && mode != MTP_FS_READ_WRITE
|
||||
&& mode != MTP_FS_APPEND)) return 0;
|
||||
std::lock_guard<std::mutex> lock(mtpMutex);
|
||||
int total = 0;
|
||||
while (total < len) {
|
||||
const int chunk = std::min(mtpWriteSize, len - total);
|
||||
if (!SendRequest(static_cast<std::uint64_t>(offset) + total,
|
||||
static_cast<unsigned int>(chunk), OP_WRITE, name.c_str())
|
||||
|| mtpTCP.WriteBlocking(static_cast<const unsigned char*>(buffer)
|
||||
+ total, chunk, mtpTimeout) != chunk) {
|
||||
mtpTCP.Close();
|
||||
break;
|
||||
}
|
||||
unsigned char countBytes[4] = {};
|
||||
if (mtpTCP.ReadBlocking(countBytes, sizeof(countBytes), mtpTimeout)
|
||||
!= sizeof(countBytes)) {
|
||||
mtpTCP.Close();
|
||||
break;
|
||||
}
|
||||
const int accepted = static_cast<int>(GetUInt32BE(countBytes));
|
||||
if (accepted <= 0 || accepted > chunk) {
|
||||
mtpTCP.Close();
|
||||
break;
|
||||
}
|
||||
total += accepted;
|
||||
if (accepted != chunk) break;
|
||||
}
|
||||
position = static_cast<std::uint64_t>(offset) + total;
|
||||
length = std::max(length, position);
|
||||
return total;
|
||||
}
|
||||
|
||||
void idFile_MTP::SetLength(const unsigned int len) {
|
||||
std::lock_guard<std::mutex> lock(mtpMutex);
|
||||
if (SendRequest(0, len, OP_SET_LENGTH, name.c_str())) {
|
||||
length = len;
|
||||
if (position > length) position = length;
|
||||
}
|
||||
}
|
||||
|
||||
int idFile_MTP::Length() const {
|
||||
return length > 0x7FFFFFFFu ? 0x7FFFFFFF : static_cast<int>(length);
|
||||
}
|
||||
|
||||
int idFile_MTP::Tell() const {
|
||||
return position > 0x7FFFFFFFu ? 0x7FFFFFFF : static_cast<int>(position);
|
||||
}
|
||||
|
||||
int idFile_MTP::Seek(const long offset, const fsOrigin_t origin) {
|
||||
std::int64_t target = 0;
|
||||
if (origin == FS_SEEK_CUR) {
|
||||
target = static_cast<std::int64_t>(position) + offset;
|
||||
} else if (origin == FS_SEEK_END) {
|
||||
target = static_cast<std::int64_t>(length) + offset;
|
||||
} else if (origin == FS_SEEK_SET) {
|
||||
target = offset;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
if (target < 0) return -1;
|
||||
position = static_cast<std::uint64_t>(target);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool idFile_MTP::List(const char* directory, const char* extension,
|
||||
idList<idStr>& list) {
|
||||
const char* const safeDirectory = directory == nullptr ? "" : directory;
|
||||
const char* const safeExtension = extension == nullptr ? "" : extension;
|
||||
fullPath.Format("%s\\*%s", safeDirectory, safeExtension);
|
||||
name = fullPath;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mtpMutex);
|
||||
if (!SendRequest(0, 0, OP_LIST, name.c_str())) return false;
|
||||
unsigned char lengthBytes[4] = {};
|
||||
if (mtpTCP.ReadBlocking(lengthBytes, sizeof(lengthBytes), mtpTimeout)
|
||||
!= sizeof(lengthBytes)) {
|
||||
mtpTCP.Close();
|
||||
return false;
|
||||
}
|
||||
const unsigned int byteCount = GetUInt32BE(lengthBytes);
|
||||
if (byteCount > 16u * 1024u * 1024u) return false;
|
||||
std::vector<char> entries(byteCount);
|
||||
if (byteCount > 0 && mtpTCP.ReadBlocking(entries.data(),
|
||||
static_cast<int>(byteCount), mtpTimeout)
|
||||
!= static_cast<int>(byteCount)) {
|
||||
mtpTCP.Close();
|
||||
return false;
|
||||
}
|
||||
unsigned int cursor = 0;
|
||||
while (cursor < byteCount) {
|
||||
const char* const entry = entries.data() + cursor;
|
||||
const std::size_t remaining = byteCount - cursor;
|
||||
const std::size_t entryLength = strnlen(entry, remaining);
|
||||
if (entryLength == remaining) return false;
|
||||
list.Append(idStr(entry));
|
||||
cursor += static_cast<unsigned int>(entryLength + 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include "idlib/precompiled.h"
|
||||
|
||||
#ifdef nullptr
|
||||
#undef nullptr
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum mtpFileMode_t {
|
||||
MTP_FS_READ = 0,
|
||||
MTP_FS_WRITE = 1,
|
||||
MTP_FS_READ_WRITE = 2,
|
||||
MTP_FS_READ_NO_BUFFERING = 3,
|
||||
MTP_FS_APPEND = 4
|
||||
};
|
||||
|
||||
class idFile_MTP : public idFile {
|
||||
public:
|
||||
idFile_MTP();
|
||||
~idFile_MTP() override;
|
||||
|
||||
const char* GetName() const override { return name.c_str(); }
|
||||
const char* GetFullPath() const override { return fullPath.c_str(); }
|
||||
int Read(void* buffer, int len) override;
|
||||
int Write(const void* buffer, int len) override;
|
||||
int Length() const override;
|
||||
ID_TIME_T Timestamp() const override { return timestamp; }
|
||||
int Tell() const override;
|
||||
int Seek(long offset, fsOrigin_t origin) override;
|
||||
|
||||
bool Open(const char* filename, mtpFileMode_t openMode);
|
||||
int ReadOfs(std::int64_t offset, void* buffer, int len);
|
||||
int WriteOfs(std::int64_t offset, const void* buffer, int len);
|
||||
void SetLength(unsigned int len);
|
||||
std::uint64_t Length64() const { return length; }
|
||||
std::uint64_t Tell64() const { return position; }
|
||||
bool List(const char* directory, const char* extension,
|
||||
idList<idStr>& list);
|
||||
|
||||
static void ConfigureServer(const char* host,
|
||||
unsigned short port = 2769, int writeSize = 1400,
|
||||
int timeoutMilliseconds = 1000);
|
||||
static void ShutdownTransport();
|
||||
|
||||
private:
|
||||
enum operation_t : unsigned short {
|
||||
OP_READ = 0,
|
||||
OP_LIST = 1,
|
||||
OP_WRITE = 2,
|
||||
OP_SET_LENGTH = 3,
|
||||
OP_OPEN = 4
|
||||
};
|
||||
|
||||
static bool EnsureConnection();
|
||||
static bool SendRequest(std::uint64_t offset, unsigned int requestLength,
|
||||
operation_t operation, const char* filename);
|
||||
|
||||
// BFG's idFile predates the recovered idTech 5 uniqID field.
|
||||
unsigned int uniqID;
|
||||
idStr name;
|
||||
idStr fullPath;
|
||||
std::uint64_t position;
|
||||
mtpFileMode_t mode;
|
||||
std::uint64_t length;
|
||||
unsigned int timestamp;
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idFile_MTP) == 104,
|
||||
"Recovered idFile_MTP ABI changed");
|
||||
#endif
|
||||
@@ -0,0 +1,413 @@
|
||||
#include "file_nfs.h"
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <climits>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
struct MountEntry {
|
||||
std::string alias;
|
||||
std::wstring root;
|
||||
bool readOnly;
|
||||
};
|
||||
|
||||
struct FileState {
|
||||
HANDLE handle = INVALID_HANDLE_VALUE;
|
||||
std::mutex mutex;
|
||||
std::wstring nativePath;
|
||||
~FileState() {
|
||||
if (handle != INVALID_HANDLE_VALUE) CloseHandle(handle);
|
||||
}
|
||||
};
|
||||
|
||||
std::mutex mountsMutex;
|
||||
std::vector<MountEntry> mounts;
|
||||
|
||||
std::string Lower(std::string text) {
|
||||
std::transform(text.begin(), text.end(), text.begin(), [](unsigned char c) {
|
||||
return static_cast<char>(std::tolower(c));
|
||||
});
|
||||
return text;
|
||||
}
|
||||
|
||||
std::wstring Wide(const char* text) {
|
||||
if (text == nullptr || *text == 0) return std::wstring();
|
||||
int amount = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
|
||||
text, -1, nullptr, 0);
|
||||
UINT codePage = CP_UTF8;
|
||||
DWORD flags = MB_ERR_INVALID_CHARS;
|
||||
if (amount == 0) {
|
||||
codePage = CP_ACP;
|
||||
flags = 0;
|
||||
amount = MultiByteToWideChar(codePage, flags, text, -1, nullptr, 0);
|
||||
}
|
||||
if (amount <= 0) return std::wstring();
|
||||
std::wstring result(static_cast<std::size_t>(amount), L'\0');
|
||||
MultiByteToWideChar(codePage, flags, text, -1, &result[0], amount);
|
||||
result.resize(static_cast<std::size_t>(amount - 1));
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring FullPath(const std::wstring& path) {
|
||||
const DWORD required = GetFullPathNameW(path.c_str(), 0, nullptr, nullptr);
|
||||
if (required == 0) return std::wstring();
|
||||
std::wstring result(static_cast<std::size_t>(required), L'\0');
|
||||
const DWORD amount = GetFullPathNameW(path.c_str(), required,
|
||||
&result[0], nullptr);
|
||||
if (amount == 0 || amount >= required) return std::wstring();
|
||||
result.resize(amount);
|
||||
while (result.size() > 3
|
||||
&& (result.back() == L'\\' || result.back() == L'/')) {
|
||||
result.pop_back();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool PathStartsWith(const std::wstring& path, const std::wstring& root) {
|
||||
if (path.size() < root.size()) return false;
|
||||
if (_wcsnicmp(path.c_str(), root.c_str(), root.size()) != 0) return false;
|
||||
return path.size() == root.size() || path[root.size()] == L'\\'
|
||||
|| path[root.size()] == L'/';
|
||||
}
|
||||
|
||||
bool Resolve(const char* nfsPath, std::wstring& nativePath,
|
||||
bool* readOnly = nullptr, std::string* aliasResult = nullptr) {
|
||||
std::string path = nfsPath == nullptr ? "" : nfsPath;
|
||||
if (path.size() >= 4 && Lower(path.substr(0, 4)) == "nfs:") {
|
||||
path.erase(0, 4);
|
||||
}
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
while (!path.empty() && path.front() == '/') path.erase(path.begin());
|
||||
const std::size_t slash = path.find('/');
|
||||
const std::string alias = Lower(path.substr(0, slash));
|
||||
const std::string relative = slash == std::string::npos
|
||||
? std::string() : path.substr(slash + 1);
|
||||
if (alias.empty()) return false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mountsMutex);
|
||||
for (const MountEntry& mount : mounts) {
|
||||
if (mount.alias != alias) continue;
|
||||
std::wstring candidate = mount.root;
|
||||
if (!relative.empty()) {
|
||||
candidate.push_back(L'\\');
|
||||
std::wstring tail = Wide(relative.c_str());
|
||||
std::replace(tail.begin(), tail.end(), L'/', L'\\');
|
||||
candidate += tail;
|
||||
}
|
||||
candidate = FullPath(candidate);
|
||||
if (candidate.empty() || !PathStartsWith(candidate, mount.root)) {
|
||||
return false;
|
||||
}
|
||||
nativePath = candidate;
|
||||
if (readOnly != nullptr) *readOnly = mount.readOnly;
|
||||
if (aliasResult != nullptr) *aliasResult = alias;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CreateDirectories(const std::wstring& path, const bool includeLast) {
|
||||
std::wstring target = path;
|
||||
if (!includeLast) {
|
||||
const std::size_t slash = target.find_last_of(L"\\/");
|
||||
if (slash == std::wstring::npos) return true;
|
||||
target.resize(slash);
|
||||
}
|
||||
const std::size_t rootEnd = target.size() >= 3 && target[1] == L':' ? 3 : 0;
|
||||
for (std::size_t index = rootEnd; index <= target.size(); ++index) {
|
||||
if (index != target.size() && target[index] != L'\\'
|
||||
&& target[index] != L'/') continue;
|
||||
const std::wstring part = target.substr(0, index);
|
||||
if (part.empty()) continue;
|
||||
if (!CreateDirectoryW(part.c_str(), nullptr)) {
|
||||
const DWORD error = GetLastError();
|
||||
if (error != ERROR_ALREADY_EXISTS) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RefreshMetadata(FileState& state, std::uint64_t& size,
|
||||
unsigned int& timestamp) {
|
||||
LARGE_INTEGER length{};
|
||||
FILETIME writeTime{};
|
||||
if (!GetFileSizeEx(state.handle, &length)
|
||||
|| !GetFileTime(state.handle, nullptr, nullptr, &writeTime)) return false;
|
||||
size = static_cast<std::uint64_t>(length.QuadPart);
|
||||
ULARGE_INTEGER ticks{};
|
||||
ticks.LowPart = writeTime.dwLowDateTime;
|
||||
ticks.HighPart = writeTime.dwHighDateTime;
|
||||
constexpr std::uint64_t epoch = 116444736000000000ULL;
|
||||
timestamp = ticks.QuadPart > epoch
|
||||
? static_cast<unsigned int>((ticks.QuadPart - epoch) / 10000000ULL) : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasExtension(const std::wstring& name, const char* extension) {
|
||||
if (extension == nullptr || *extension == 0
|
||||
|| std::strcmp(extension, "*") == 0) return true;
|
||||
const std::wstring suffix = Wide(extension);
|
||||
return name.size() >= suffix.size()
|
||||
&& _wcsicmp(name.c_str() + name.size() - suffix.size(),
|
||||
suffix.c_str()) == 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
idFile_Nfs::idFile_Nfs()
|
||||
: uniqID(0), openRemote(false), openPadding{}, fh{},
|
||||
fullPath("invalid"), mode(NFS_FS_READ), position(0), size(0),
|
||||
timeStamp(0), demandSeek(false), seekPadding{}, nfsClient(nullptr),
|
||||
ro(false) {}
|
||||
|
||||
idFile_Nfs::~idFile_Nfs() {
|
||||
delete static_cast<FileState*>(nfsClient);
|
||||
nfsClient = nullptr;
|
||||
openRemote = false;
|
||||
}
|
||||
|
||||
bool idFile_Nfs::Mount(const char* alias, const char* windowsRoot,
|
||||
const bool readOnly) {
|
||||
std::string normalized = Lower(alias == nullptr ? "" : alias);
|
||||
while (!normalized.empty() && normalized.front() == '/') {
|
||||
normalized.erase(normalized.begin());
|
||||
}
|
||||
while (!normalized.empty() && normalized.back() == '/') normalized.pop_back();
|
||||
if (normalized.empty() || normalized.find('/') != std::string::npos
|
||||
|| normalized.find('\\') != std::string::npos) return false;
|
||||
const std::wstring root = FullPath(Wide(windowsRoot));
|
||||
if (root.empty()) return false;
|
||||
const DWORD attributes = GetFileAttributesW(root.c_str());
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES
|
||||
|| (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) return false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mountsMutex);
|
||||
for (MountEntry& mount : mounts) {
|
||||
if (mount.alias == normalized) {
|
||||
mount.root = root;
|
||||
mount.readOnly = readOnly;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
mounts.push_back({normalized, root, readOnly});
|
||||
return true;
|
||||
}
|
||||
|
||||
void idFile_Nfs::UnmountAll() {
|
||||
std::lock_guard<std::mutex> lock(mountsMutex);
|
||||
mounts.clear();
|
||||
}
|
||||
|
||||
bool idFile_Nfs::Open(const char* path, const nfsFileMode_t openMode,
|
||||
bool create, const bool createPath) {
|
||||
delete static_cast<FileState*>(nfsClient);
|
||||
nfsClient = nullptr;
|
||||
openRemote = false;
|
||||
|
||||
std::wstring native;
|
||||
bool mountReadOnly = false;
|
||||
if (!Resolve(path, native, &mountReadOnly)) return false;
|
||||
const bool wantsWrite = openMode == NFS_FS_WRITE
|
||||
|| openMode == NFS_FS_READ_WRITE || openMode == NFS_FS_APPEND;
|
||||
if (mountReadOnly && wantsWrite) return false;
|
||||
if (openMode == NFS_FS_WRITE || openMode == NFS_FS_APPEND) create = true;
|
||||
if (createPath && create && !CreateDirectories(native, false)) return false;
|
||||
|
||||
DWORD access = GENERIC_READ;
|
||||
DWORD disposition = OPEN_EXISTING;
|
||||
if (openMode == NFS_FS_WRITE) {
|
||||
access = GENERIC_WRITE | GENERIC_READ;
|
||||
disposition = CREATE_ALWAYS;
|
||||
} else if (openMode == NFS_FS_READ_WRITE) {
|
||||
access = GENERIC_WRITE | GENERIC_READ;
|
||||
disposition = create ? OPEN_ALWAYS : OPEN_EXISTING;
|
||||
} else if (openMode == NFS_FS_APPEND) {
|
||||
access = GENERIC_WRITE | GENERIC_READ;
|
||||
disposition = OPEN_ALWAYS;
|
||||
}
|
||||
|
||||
FileState* const state = new FileState;
|
||||
state->nativePath = native;
|
||||
state->handle = CreateFileW(native.c_str(), access,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
||||
disposition, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (state->handle == INVALID_HANDLE_VALUE) {
|
||||
delete state;
|
||||
return false;
|
||||
}
|
||||
nfsClient = state;
|
||||
openRemote = true;
|
||||
ro = mountReadOnly;
|
||||
mode = openMode;
|
||||
fullPath = path == nullptr ? "" : path;
|
||||
if (!RefreshMetadata(*state, size, timeStamp)) {
|
||||
delete state;
|
||||
nfsClient = nullptr;
|
||||
openRemote = false;
|
||||
return false;
|
||||
}
|
||||
position = openMode == NFS_FS_APPEND ? size : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
int idFile_Nfs::ReadOfs(const std::int64_t offset, void* buffer,
|
||||
const int len) {
|
||||
FileState* const state = static_cast<FileState*>(nfsClient);
|
||||
if (state == nullptr || buffer == nullptr || len <= 0 || offset < 0) return 0;
|
||||
std::lock_guard<std::mutex> lock(state->mutex);
|
||||
LARGE_INTEGER saved{}, target{};
|
||||
target.QuadPart = offset;
|
||||
if (!SetFilePointerEx(state->handle, {}, &saved, FILE_CURRENT)
|
||||
|| !SetFilePointerEx(state->handle, target, nullptr, FILE_BEGIN)) return 0;
|
||||
DWORD amount = 0;
|
||||
const BOOL success = ReadFile(state->handle, buffer,
|
||||
static_cast<DWORD>(len), &amount, nullptr);
|
||||
SetFilePointerEx(state->handle, saved, nullptr, FILE_BEGIN);
|
||||
return success ? static_cast<int>(amount) : 0;
|
||||
}
|
||||
|
||||
int idFile_Nfs::WriteOfs(const std::int64_t offset, const void* buffer,
|
||||
const int len) {
|
||||
FileState* const state = static_cast<FileState*>(nfsClient);
|
||||
if (state == nullptr || ro || buffer == nullptr || len <= 0 || offset < 0) return 0;
|
||||
std::lock_guard<std::mutex> lock(state->mutex);
|
||||
LARGE_INTEGER saved{}, target{};
|
||||
target.QuadPart = offset;
|
||||
if (!SetFilePointerEx(state->handle, {}, &saved, FILE_CURRENT)
|
||||
|| !SetFilePointerEx(state->handle, target, nullptr, FILE_BEGIN)) return 0;
|
||||
DWORD amount = 0;
|
||||
const BOOL success = WriteFile(state->handle, buffer,
|
||||
static_cast<DWORD>(len), &amount, nullptr);
|
||||
SetFilePointerEx(state->handle, saved, nullptr, FILE_BEGIN);
|
||||
if (success) {
|
||||
size = std::max(size, static_cast<std::uint64_t>(offset) + amount);
|
||||
RefreshMetadata(*state, size, timeStamp);
|
||||
}
|
||||
return success ? static_cast<int>(amount) : 0;
|
||||
}
|
||||
|
||||
int idFile_Nfs::Read(void* buffer, const int len) {
|
||||
const int amount = ReadOfs(static_cast<std::int64_t>(position), buffer, len);
|
||||
position += static_cast<std::uint64_t>(amount);
|
||||
return amount;
|
||||
}
|
||||
|
||||
int idFile_Nfs::Write(const void* buffer, const int len) {
|
||||
if (mode == NFS_FS_APPEND) position = size;
|
||||
const int amount = WriteOfs(static_cast<std::int64_t>(position), buffer, len);
|
||||
position += static_cast<std::uint64_t>(amount);
|
||||
return amount;
|
||||
}
|
||||
|
||||
int idFile_Nfs::Length() const {
|
||||
return size > static_cast<std::uint64_t>(INT_MAX) ? INT_MAX
|
||||
: static_cast<int>(size);
|
||||
}
|
||||
|
||||
int idFile_Nfs::Tell() const {
|
||||
return position > static_cast<std::uint64_t>(INT_MAX) ? INT_MAX
|
||||
: static_cast<int>(position);
|
||||
}
|
||||
|
||||
int idFile_Nfs::Seek(const long offset, const fsOrigin_t origin) {
|
||||
return Seek64(offset, origin);
|
||||
}
|
||||
|
||||
int idFile_Nfs::Seek64(const std::int64_t offset, const fsOrigin_t origin) {
|
||||
std::int64_t base = 0;
|
||||
if (origin == FS_SEEK_CUR) base = static_cast<std::int64_t>(position);
|
||||
else if (origin == FS_SEEK_END) base = static_cast<std::int64_t>(size);
|
||||
const std::int64_t next = base + offset;
|
||||
if (next < 0) return -1;
|
||||
position = static_cast<std::uint64_t>(next);
|
||||
demandSeek = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void idFile_Nfs::SetLength(const unsigned int len) { SetLength64(len); }
|
||||
|
||||
bool idFile_Nfs::SetLength64(const std::uint64_t len) {
|
||||
FileState* const state = static_cast<FileState*>(nfsClient);
|
||||
if (state == nullptr || ro || len > static_cast<std::uint64_t>(INT64_MAX)) return false;
|
||||
std::lock_guard<std::mutex> lock(state->mutex);
|
||||
LARGE_INTEGER saved{}, target{};
|
||||
target.QuadPart = static_cast<LONGLONG>(len);
|
||||
if (!SetFilePointerEx(state->handle, {}, &saved, FILE_CURRENT)
|
||||
|| !SetFilePointerEx(state->handle, target, nullptr, FILE_BEGIN)
|
||||
|| !SetEndOfFile(state->handle)) return false;
|
||||
SetFilePointerEx(state->handle, saved, nullptr, FILE_BEGIN);
|
||||
size = len;
|
||||
if (position > size) position = size;
|
||||
RefreshMetadata(*state, size, timeStamp);
|
||||
return true;
|
||||
}
|
||||
|
||||
void idFile_Nfs::Flush() {
|
||||
FileState* const state = static_cast<FileState*>(nfsClient);
|
||||
if (state != nullptr && !ro) FlushFileBuffers(state->handle);
|
||||
}
|
||||
|
||||
void idFile_Nfs::ForceFlush() { Flush(); }
|
||||
|
||||
bool idFile_Nfs::CreateOsPath(const char* path) {
|
||||
std::wstring native;
|
||||
bool readOnly = false;
|
||||
return Resolve(path, native, &readOnly) && !readOnly
|
||||
&& CreateDirectories(native, false);
|
||||
}
|
||||
|
||||
int idFile_Nfs::ListFiles(const char* path, const char* extension,
|
||||
idList<idStr>& list) {
|
||||
list.Clear();
|
||||
std::wstring native;
|
||||
if (!Resolve(path, native)) return 0;
|
||||
std::wstring query = native;
|
||||
if (!query.empty() && query.back() != L'\\') query.push_back(L'\\');
|
||||
query.push_back(L'*');
|
||||
WIN32_FIND_DATAW data{};
|
||||
HANDLE find = FindFirstFileW(query.c_str(), &data);
|
||||
if (find == INVALID_HANDLE_VALUE) return 0;
|
||||
do {
|
||||
const std::wstring name = data.cFileName;
|
||||
if (name == L"." || name == L".." || !HasExtension(name, extension)) continue;
|
||||
const int amount = WideCharToMultiByte(CP_UTF8, 0, name.c_str(), -1,
|
||||
nullptr, 0, nullptr, nullptr);
|
||||
if (amount <= 1) continue;
|
||||
std::string utf8(static_cast<std::size_t>(amount), '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, name.c_str(), -1, &utf8[0], amount,
|
||||
nullptr, nullptr);
|
||||
utf8.resize(static_cast<std::size_t>(amount - 1));
|
||||
list.Append(idStr(utf8.c_str()));
|
||||
} while (FindNextFileW(find, &data));
|
||||
FindClose(find);
|
||||
return list.Num();
|
||||
}
|
||||
|
||||
bool idFile_Nfs::RemoveFile(const char* path) {
|
||||
std::wstring native;
|
||||
bool readOnly = false;
|
||||
if (!Resolve(path, native, &readOnly) || readOnly) return false;
|
||||
const DWORD attributes = GetFileAttributesW(native.c_str());
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES) return false;
|
||||
return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0
|
||||
? RemoveDirectoryW(native.c_str()) != FALSE
|
||||
: DeleteFileW(native.c_str()) != FALSE;
|
||||
}
|
||||
|
||||
bool idFile_Nfs::RenameFile(const char* oldPath, const char* newPath) {
|
||||
std::wstring oldNative, newNative;
|
||||
bool oldReadOnly = false, newReadOnly = false;
|
||||
std::string oldAlias, newAlias;
|
||||
if (!Resolve(oldPath, oldNative, &oldReadOnly, &oldAlias)
|
||||
|| !Resolve(newPath, newNative, &newReadOnly, &newAlias)
|
||||
|| oldReadOnly || newReadOnly || oldAlias != newAlias) return false;
|
||||
return MoveFileExW(oldNative.c_str(), newNative.c_str(),
|
||||
MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != FALSE;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include "idlib/precompiled.h"
|
||||
|
||||
#ifdef nullptr
|
||||
#undef nullptr
|
||||
#endif
|
||||
#ifdef strcmp
|
||||
#undef strcmp
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum nfsFileMode_t {
|
||||
NFS_FS_READ = 0,
|
||||
NFS_FS_WRITE = 1,
|
||||
NFS_FS_READ_WRITE = 2,
|
||||
NFS_FS_READ_NO_BUFFERING = 3,
|
||||
NFS_FS_APPEND = 4
|
||||
};
|
||||
|
||||
class idFile_Nfs : public idFile {
|
||||
public:
|
||||
idFile_Nfs();
|
||||
~idFile_Nfs() override;
|
||||
|
||||
const char* GetName() const override { return fullPath.c_str(); }
|
||||
const char* GetFullPath() const override { return fullPath.c_str(); }
|
||||
int Read(void* buffer, int len) override;
|
||||
int Write(const void* buffer, int len) override;
|
||||
int Length() const override;
|
||||
ID_TIME_T Timestamp() const override { return timeStamp; }
|
||||
int Tell() const override;
|
||||
int Seek(long offset, fsOrigin_t origin) override;
|
||||
void Flush() override;
|
||||
void ForceFlush() override;
|
||||
|
||||
bool Open(const char* path, nfsFileMode_t openMode, bool create = false,
|
||||
bool createPath = false);
|
||||
int ReadOfs(std::int64_t offset, void* buffer, int len);
|
||||
int WriteOfs(std::int64_t offset, const void* buffer, int len);
|
||||
void SetLength(unsigned int len);
|
||||
bool SetLength64(std::uint64_t len);
|
||||
std::uint64_t Length64() const { return size; }
|
||||
std::uint64_t Tell64() const { return position; }
|
||||
int Seek64(std::int64_t offset, fsOrigin_t origin);
|
||||
bool IsReadOnly() const { return ro; }
|
||||
|
||||
static bool Mount(const char* alias, const char* windowsRoot,
|
||||
bool readOnly = false);
|
||||
static void UnmountAll();
|
||||
static bool CreateOsPath(const char* path);
|
||||
static int ListFiles(const char* path, const char* extension,
|
||||
idList<idStr>& list);
|
||||
static bool RemoveFile(const char* path);
|
||||
static bool RenameFile(const char* oldPath, const char* newPath);
|
||||
|
||||
private:
|
||||
struct NfsInternalFh { std::uint32_t pad[38]; };
|
||||
|
||||
unsigned int uniqID;
|
||||
bool openRemote;
|
||||
unsigned char openPadding[3];
|
||||
NfsInternalFh fh;
|
||||
idStr fullPath;
|
||||
nfsFileMode_t mode;
|
||||
std::uint64_t position;
|
||||
std::uint64_t size;
|
||||
unsigned int timeStamp;
|
||||
bool demandSeek;
|
||||
unsigned char seekPadding[3];
|
||||
void* nfsClient;
|
||||
bool ro;
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idFile_Nfs) == 232,
|
||||
"Recovered idFile_Nfs ABI changed");
|
||||
#endif
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
// The portable permanent-file implementation currently comes from the BFG
|
||||
// framework baseline. This compatibility include restores the original
|
||||
// shared/idlib path used by tungsten sources while its wider idTech 5 layout
|
||||
// is introduced incrementally at the filesystem boundary.
|
||||
#include "idlib/precompiled.h"
|
||||
|
||||
#ifdef nullptr
|
||||
#undef nullptr
|
||||
#endif
|
||||
|
||||
inline int idFilePermanentSectorSize(const idFile_Permanent&) { return 1; }
|
||||
@@ -0,0 +1,247 @@
|
||||
#include "warningsfilter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
std::mutex filterMutex;
|
||||
|
||||
struct filterConfig_t {
|
||||
const mgWarningsFilter* owner;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
filterConfig_t filterConfigs[8] = {};
|
||||
|
||||
bool& EnabledFor(const mgWarningsFilter* owner) {
|
||||
for (filterConfig_t& config : filterConfigs) {
|
||||
if (config.owner == owner) return config.enabled;
|
||||
}
|
||||
for (filterConfig_t& config : filterConfigs) {
|
||||
if (config.owner == nullptr) {
|
||||
config.owner = owner;
|
||||
config.enabled = true;
|
||||
return config.enabled;
|
||||
}
|
||||
}
|
||||
return filterConfigs[0].enabled;
|
||||
}
|
||||
|
||||
bool DecodeJSONString(const char* objectBegin, const char* objectEnd,
|
||||
const char* key, std::string& result) {
|
||||
const std::string needle = std::string("\"") + key + "\"";
|
||||
const char* keyPosition = std::search(objectBegin, objectEnd,
|
||||
needle.begin(), needle.end());
|
||||
if (keyPosition == objectEnd) return false;
|
||||
const char* cursor = keyPosition + needle.size();
|
||||
while (cursor < objectEnd && *cursor != ':') ++cursor;
|
||||
if (cursor == objectEnd) return false;
|
||||
while (++cursor < objectEnd && (*cursor == ' ' || *cursor == '\t'
|
||||
|| *cursor == '\r' || *cursor == '\n')) {}
|
||||
if (cursor == objectEnd || *cursor != '"') return false;
|
||||
++cursor;
|
||||
result.clear();
|
||||
while (cursor < objectEnd && *cursor != '"') {
|
||||
if (*cursor == '\\' && cursor + 1 < objectEnd) {
|
||||
++cursor;
|
||||
switch (*cursor) {
|
||||
case 'n': result.push_back('\n'); break;
|
||||
case 'r': result.push_back('\r'); break;
|
||||
case 't': result.push_back('\t'); break;
|
||||
default: result.push_back(*cursor); break;
|
||||
}
|
||||
} else {
|
||||
result.push_back(*cursor);
|
||||
}
|
||||
++cursor;
|
||||
}
|
||||
return cursor < objectEnd;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
warningfilter_t::warningfilter_t() : reg{} {}
|
||||
|
||||
warningfilter_t::warningfilter_t(const warningfilter_t& other)
|
||||
: pattern(other.pattern), owner(other.owner), reg{} {
|
||||
if (other.reg.value != nullptr) {
|
||||
try {
|
||||
reg.value = new std::regex(pattern.c_str(),
|
||||
std::regex_constants::extended);
|
||||
} catch (const std::regex_error&) {
|
||||
reg.value = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
warningfilter_t::~warningfilter_t() {
|
||||
delete static_cast<std::regex*>(reg.value);
|
||||
}
|
||||
|
||||
warningfilter_t& warningfilter_t::operator=(const warningfilter_t& other) {
|
||||
if (this == &other) return *this;
|
||||
delete static_cast<std::regex*>(reg.value);
|
||||
reg = {};
|
||||
pattern = other.pattern;
|
||||
owner = other.owner;
|
||||
if (other.reg.value != nullptr) {
|
||||
try {
|
||||
reg.value = new std::regex(pattern.c_str(),
|
||||
std::regex_constants::extended);
|
||||
} catch (const std::regex_error&) {
|
||||
reg.value = nullptr;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
mgWarningsFilter::mgWarningsFilter() : filters{}, init(false) {
|
||||
filters.granularity = 16;
|
||||
EnabledFor(this) = true;
|
||||
}
|
||||
|
||||
mgWarningsFilter::~mgWarningsFilter() {
|
||||
Clear();
|
||||
for (filterConfig_t& config : filterConfigs) {
|
||||
if (config.owner == this) config = {};
|
||||
}
|
||||
}
|
||||
|
||||
bool mgWarningsFilter::EnsureCapacity(const int amount) {
|
||||
if (amount <= filters.size) return true;
|
||||
int newSize = filters.size == 0 ? filters.granularity : filters.size;
|
||||
while (newSize < amount) newSize += filters.granularity;
|
||||
warningfilter_t* const replacement = new (std::nothrow)
|
||||
warningfilter_t[newSize];
|
||||
if (replacement == nullptr) return false;
|
||||
for (int index = 0; index < filters.num; ++index) {
|
||||
replacement[index] = filters.list[index];
|
||||
}
|
||||
delete[] filters.list;
|
||||
filters.list = replacement;
|
||||
filters.size = newSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mgWarningsFilter::AddFilter(const char* patternText,
|
||||
const char* ownerText) {
|
||||
if (patternText == nullptr || *patternText == '\0') return false;
|
||||
std::regex* compiled = nullptr;
|
||||
try {
|
||||
compiled = new std::regex(patternText, std::regex_constants::extended);
|
||||
} catch (const std::regex_error&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(filterMutex);
|
||||
if (!EnsureCapacity(filters.num + 1)) {
|
||||
delete compiled;
|
||||
return false;
|
||||
}
|
||||
warningfilter_t& filter = filters.list[filters.num++];
|
||||
filter.pattern = patternText;
|
||||
filter.owner = ownerText == nullptr ? "" : ownerText;
|
||||
filter.reg.re_nsub = 0;
|
||||
filter.reg.value = compiled;
|
||||
init = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
int mgWarningsFilter::RemoveOwner(const char* ownerText) {
|
||||
const char* const safeOwner = ownerText == nullptr ? "" : ownerText;
|
||||
std::lock_guard<std::mutex> lock(filterMutex);
|
||||
int removed = 0;
|
||||
for (int index = 0; index < filters.num;) {
|
||||
if (std::strcmp(filters.list[index].owner.c_str(), safeOwner) != 0) {
|
||||
++index;
|
||||
continue;
|
||||
}
|
||||
for (int move = index + 1; move < filters.num; ++move) {
|
||||
filters.list[move - 1] = filters.list[move];
|
||||
}
|
||||
filters.list[filters.num - 1] = warningfilter_t();
|
||||
--filters.num;
|
||||
++removed;
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
void mgWarningsFilter::Clear() {
|
||||
std::lock_guard<std::mutex> lock(filterMutex);
|
||||
delete[] filters.list;
|
||||
filters.list = nullptr;
|
||||
filters.num = 0;
|
||||
filters.size = 0;
|
||||
init = false;
|
||||
}
|
||||
|
||||
bool mgWarningsFilter::IsFiltered(const char* warning, idStr* matchedOwner) const {
|
||||
if (warning == nullptr || !EnabledFor(this)) return false;
|
||||
std::lock_guard<std::mutex> lock(filterMutex);
|
||||
for (int index = 0; index < filters.num; ++index) {
|
||||
const warningfilter_t& filter = filters.list[index];
|
||||
const std::regex* const expression =
|
||||
static_cast<const std::regex*>(filter.reg.value);
|
||||
if (expression != nullptr && std::regex_search(warning, *expression)) {
|
||||
if (matchedOwner != nullptr) *matchedOwner = filter.owner;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const warningfilter_t* mgWarningsFilter::GetFilter(const int index) const {
|
||||
return index >= 0 && index < filters.num ? filters.list + index : nullptr;
|
||||
}
|
||||
|
||||
void mgWarningsFilter::SetEnabled(const bool value) {
|
||||
EnabledFor(this) = value;
|
||||
}
|
||||
|
||||
bool mgWarningsFilter::IsEnabled() const {
|
||||
return EnabledFor(this);
|
||||
}
|
||||
|
||||
int mgWarningsFilter::LoadJSONText(const char* json) {
|
||||
if (json == nullptr) return 0;
|
||||
int loaded = 0;
|
||||
const char* cursor = json;
|
||||
while ((cursor = std::strchr(cursor, '{')) != nullptr) {
|
||||
const char* const end = std::strchr(cursor + 1, '}');
|
||||
if (end == nullptr) break;
|
||||
std::string patternText;
|
||||
std::string ownerText;
|
||||
if (DecodeJSONString(cursor, end, "pattern", patternText)) {
|
||||
DecodeJSONString(cursor, end, "owner", ownerText);
|
||||
if (AddFilter(patternText.c_str(), ownerText.c_str())) ++loaded;
|
||||
}
|
||||
cursor = end + 1;
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
bool mgWarningsFilter::LoadJSONFile(const char* filename) {
|
||||
if (filename == nullptr) return false;
|
||||
FILE* file = nullptr;
|
||||
if (fopen_s(&file, filename, "rb") != 0 || file == nullptr) return false;
|
||||
std::fseek(file, 0, SEEK_END);
|
||||
const long length = std::ftell(file);
|
||||
std::fseek(file, 0, SEEK_SET);
|
||||
if (length < 0 || length > 16 * 1024 * 1024) {
|
||||
std::fclose(file);
|
||||
return false;
|
||||
}
|
||||
std::string contents(static_cast<std::size_t>(length), '\0');
|
||||
const std::size_t read = contents.empty() ? 0
|
||||
: std::fread(&contents[0], 1, contents.size(), file);
|
||||
std::fclose(file);
|
||||
return read == contents.size() && LoadJSONText(contents.c_str()) > 0;
|
||||
}
|
||||
|
||||
mgWarningsFilter warningsFilter;
|
||||
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "idlib/text/str.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct warningfilter_regex_t {
|
||||
unsigned int re_nsub;
|
||||
void* value;
|
||||
};
|
||||
|
||||
struct warningfilter_t {
|
||||
warningfilter_t();
|
||||
warningfilter_t(const warningfilter_t& other);
|
||||
~warningfilter_t();
|
||||
warningfilter_t& operator=(const warningfilter_t& other);
|
||||
|
||||
idStr pattern;
|
||||
idStr owner;
|
||||
warningfilter_regex_t reg;
|
||||
};
|
||||
|
||||
class mgWarningsFilter {
|
||||
public:
|
||||
mgWarningsFilter();
|
||||
~mgWarningsFilter();
|
||||
|
||||
bool AddFilter(const char* pattern, const char* owner = "");
|
||||
int RemoveOwner(const char* owner);
|
||||
void Clear();
|
||||
bool IsFiltered(const char* warning, idStr* owner = nullptr) const;
|
||||
bool Filter(const char* warning) const { return IsFiltered(warning); }
|
||||
|
||||
bool LoadJSONFile(const char* filename);
|
||||
int LoadJSONText(const char* json);
|
||||
int NumFilters() const { return filters.num; }
|
||||
const warningfilter_t* GetFilter(int index) const;
|
||||
|
||||
void SetEnabled(bool value);
|
||||
bool IsEnabled() const;
|
||||
|
||||
private:
|
||||
struct recoveredFilterList_t {
|
||||
warningfilter_t* list;
|
||||
int num;
|
||||
int size;
|
||||
short granularity;
|
||||
unsigned char memTag;
|
||||
unsigned char listStatic;
|
||||
} filters;
|
||||
bool init;
|
||||
|
||||
// Enabled is kept out of the recovered object layout in the .cpp sidecar.
|
||||
bool EnsureCapacity(int amount);
|
||||
};
|
||||
|
||||
extern mgWarningsFilter warningsFilter;
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(warningfilter_regex_t) == 8,
|
||||
"Recovered regex_t facade ABI changed");
|
||||
static_assert(sizeof(warningfilter_t) == 72,
|
||||
"Recovered warningfilter_t ABI changed");
|
||||
static_assert(sizeof(mgWarningsFilter) == 20,
|
||||
"Recovered mgWarningsFilter ABI changed");
|
||||
#endif
|
||||
@@ -0,0 +1,227 @@
|
||||
#include "idlib/geometry/geometry.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kSmallestNormal = std::numeric_limits<float>::min();
|
||||
constexpr float kParallelEpsilonSqr = 0.0001f;
|
||||
|
||||
float ClampUnit(const float value) {
|
||||
return std::max(0.0f, std::min(1.0f, value));
|
||||
}
|
||||
|
||||
float Cross2D(const idVec2& left, const idVec2& right) {
|
||||
return left.x * right.y - left.y * right.x;
|
||||
}
|
||||
|
||||
idVec2 Subtract2D(const idVec2& left, const idVec2& right) {
|
||||
return idVec2(left.x - right.x, left.y - right.y);
|
||||
}
|
||||
|
||||
idVec3 NormalizeSafely(const idVec3& vector) {
|
||||
const float lengthSqr = vector.LengthSqr();
|
||||
if (lengthSqr < kSmallestNormal) {
|
||||
return idVec3(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
return vector * (1.0f / std::sqrt(lengthSqr));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
float idGeometry::PositionOnLineSegment(
|
||||
const idVec3& point,
|
||||
const idVec3& start,
|
||||
const idVec3& end
|
||||
) {
|
||||
const idVec3 segment = end - start;
|
||||
const float lengthSqr = segment.LengthSqr();
|
||||
if (lengthSqr < kSmallestNormal) {
|
||||
return 0.0f;
|
||||
}
|
||||
return (point - start).Dot(segment) / lengthSqr;
|
||||
}
|
||||
|
||||
bool idGeometry::ClosestPointOnLineSegment(
|
||||
const idVec3& point,
|
||||
const idVec3& start,
|
||||
const idVec3& end,
|
||||
idVec3& closest
|
||||
) {
|
||||
const idVec3 segment = end - start;
|
||||
const float lengthSqr = segment.LengthSqr();
|
||||
if (lengthSqr < kSmallestNormal) {
|
||||
closest = start;
|
||||
return false;
|
||||
}
|
||||
|
||||
const float position = (point - start).Dot(segment) / lengthSqr;
|
||||
if (position < 0.0f) {
|
||||
closest = start;
|
||||
return false;
|
||||
}
|
||||
if (position > 1.0f) {
|
||||
closest = end;
|
||||
return false;
|
||||
}
|
||||
|
||||
closest = start + segment * position;
|
||||
return true;
|
||||
}
|
||||
|
||||
void idGeometry::ClosestPointOnLine(
|
||||
const idVec3& point,
|
||||
const idVec3& start,
|
||||
const idVec3& dir,
|
||||
idVec3& closest
|
||||
) {
|
||||
closest = start + dir * (point - start).Dot(dir);
|
||||
}
|
||||
|
||||
idVec3 idGeometry::TriangleNormal(
|
||||
const idVec3& a,
|
||||
const idVec3& b,
|
||||
const idVec3& c
|
||||
) {
|
||||
// The recovered winding is intentionally (c-a) x (b-a), opposite the
|
||||
// common formulation. Renderer-facing callers depend on that sign.
|
||||
return NormalizeSafely((c - a).Cross(b - a));
|
||||
}
|
||||
|
||||
bool idGeometry::IntersectRayWithLineSegment2D(
|
||||
const idVec2& rayStart,
|
||||
const idVec2& rayDir,
|
||||
const idVec2& segStart,
|
||||
const idVec2& segEnd,
|
||||
float& dist
|
||||
) {
|
||||
const idVec2 segment = Subtract2D(segEnd, segStart);
|
||||
const idVec2 offset = Subtract2D(segStart, rayStart);
|
||||
const float denominator = Cross2D(rayDir, segment);
|
||||
|
||||
if (denominator * denominator > kParallelEpsilonSqr) {
|
||||
const float inverseDenominator = 1.0f / denominator;
|
||||
const float segmentPosition = -Cross2D(rayDir, offset)
|
||||
* inverseDenominator;
|
||||
if (segmentPosition < 0.0f || segmentPosition > 1.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float rayPosition = Cross2D(offset, segment)
|
||||
* inverseDenominator;
|
||||
if (rayPosition < 0.0f) {
|
||||
return false;
|
||||
}
|
||||
dist = rayPosition;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Cross2D(rayDir, offset) * Cross2D(rayDir, offset)
|
||||
> kParallelEpsilonSqr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The PPC output is difficult to read in this collinear branch. This is
|
||||
// the equivalent geometric result: the nearest forward overlap measured
|
||||
// in ray parameter units. Keep it isolated for later binary trace checks.
|
||||
const float rayLengthSqr = rayDir.x * rayDir.x + rayDir.y * rayDir.y;
|
||||
if (rayLengthSqr < kSmallestNormal) {
|
||||
return false;
|
||||
}
|
||||
const idVec2 endOffset = Subtract2D(segEnd, rayStart);
|
||||
const float startPosition = (offset.x * rayDir.x + offset.y * rayDir.y)
|
||||
/ rayLengthSqr;
|
||||
const float endPosition = (endOffset.x * rayDir.x + endOffset.y * rayDir.y)
|
||||
/ rayLengthSqr;
|
||||
const float nearPosition = std::min(startPosition, endPosition);
|
||||
const float farPosition = std::max(startPosition, endPosition);
|
||||
if (farPosition < 0.0f) {
|
||||
return false;
|
||||
}
|
||||
dist = std::max(0.0f, nearPosition);
|
||||
return true;
|
||||
}
|
||||
|
||||
float idGeometry::SquarePointLineSegmentDistance(
|
||||
const idVec3& point,
|
||||
const idVec3& start,
|
||||
const idVec3& end
|
||||
) {
|
||||
const idVec3 segment = end - start;
|
||||
const float lengthSqr = segment.LengthSqr();
|
||||
if (lengthSqr < 0.01f) {
|
||||
return (point - start).LengthSqr();
|
||||
}
|
||||
|
||||
const float position = ClampUnit((point - start).Dot(segment) / lengthSqr);
|
||||
return (point - (start + segment * position)).LengthSqr();
|
||||
}
|
||||
|
||||
void idGeometry::SegmentSegmentClosestPoints(
|
||||
const idVec3& start1,
|
||||
const idVec3& end1,
|
||||
const idVec3& start2,
|
||||
const idVec3& end2,
|
||||
idVec3& out1,
|
||||
idVec3& out2,
|
||||
float& t1,
|
||||
float& t2,
|
||||
const bool clampTValues
|
||||
) {
|
||||
const idVec3 direction1 = end1 - start1;
|
||||
const idVec3 direction2 = end2 - start2;
|
||||
const idVec3 offset = start1 - start2;
|
||||
const float length1Sqr = direction1.LengthSqr();
|
||||
const float length2Sqr = direction2.LengthSqr();
|
||||
const float directionsDot = direction1.Dot(direction2);
|
||||
const float offsetDot1 = direction1.Dot(offset);
|
||||
const float offsetDot2 = direction2.Dot(offset);
|
||||
const float denominator = length1Sqr * length2Sqr
|
||||
- directionsDot * directionsDot;
|
||||
|
||||
if (length1Sqr < kSmallestNormal
|
||||
|| length2Sqr < kSmallestNormal
|
||||
|| denominator < kSmallestNormal) {
|
||||
out1 = start1;
|
||||
out2 = start2;
|
||||
t1 = 1.0f;
|
||||
t2 = 1.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
t1 = (directionsDot * offsetDot2 - offsetDot1 * length2Sqr)
|
||||
/ denominator;
|
||||
if (clampTValues) {
|
||||
t1 = ClampUnit(t1);
|
||||
}
|
||||
|
||||
t2 = (t1 * directionsDot + offsetDot2) / length2Sqr;
|
||||
if (clampTValues) {
|
||||
t2 = ClampUnit(t2);
|
||||
}
|
||||
|
||||
out1 = start1 + direction1 * t1;
|
||||
out2 = start2 + direction2 * t2;
|
||||
}
|
||||
|
||||
idVec3 idGeometry::FindNearestPerpendicular(
|
||||
const idVec3& input,
|
||||
const idVec3& up,
|
||||
const idVec3& hint
|
||||
) {
|
||||
idVec3 perpendicular = input.Cross(up);
|
||||
if (perpendicular.Dot(hint) < 0.0f) {
|
||||
perpendicular = -perpendicular;
|
||||
}
|
||||
return NormalizeSafely(perpendicular);
|
||||
}
|
||||
|
||||
float idGeometry::AreaOfTriangle(
|
||||
const idVec3& a,
|
||||
const idVec3& b,
|
||||
const idVec3& c
|
||||
) {
|
||||
return 0.5f * (b - a).Cross(c - a).Length();
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include "../math/vector.h"
|
||||
|
||||
class idGeometry {
|
||||
public:
|
||||
static float PositionOnLineSegment(
|
||||
const idVec3& point,
|
||||
const idVec3& start,
|
||||
const idVec3& end
|
||||
);
|
||||
|
||||
static bool ClosestPointOnLineSegment(
|
||||
const idVec3& point,
|
||||
const idVec3& start,
|
||||
const idVec3& end,
|
||||
idVec3& closest
|
||||
);
|
||||
|
||||
static void ClosestPointOnLine(
|
||||
const idVec3& point,
|
||||
const idVec3& start,
|
||||
const idVec3& dir,
|
||||
idVec3& closest
|
||||
);
|
||||
|
||||
static idVec3 TriangleNormal(
|
||||
const idVec3& a,
|
||||
const idVec3& b,
|
||||
const idVec3& c
|
||||
);
|
||||
|
||||
static bool IntersectRayWithLineSegment2D(
|
||||
const idVec2& rayStart,
|
||||
const idVec2& rayDir,
|
||||
const idVec2& segStart,
|
||||
const idVec2& segEnd,
|
||||
float& dist
|
||||
);
|
||||
|
||||
static float SquarePointLineSegmentDistance(
|
||||
const idVec3& point,
|
||||
const idVec3& start,
|
||||
const idVec3& end
|
||||
);
|
||||
|
||||
static void SegmentSegmentClosestPoints(
|
||||
const idVec3& start1,
|
||||
const idVec3& end1,
|
||||
const idVec3& start2,
|
||||
const idVec3& end2,
|
||||
idVec3& out1,
|
||||
idVec3& out2,
|
||||
float& t1,
|
||||
float& t2,
|
||||
bool clampTValues
|
||||
);
|
||||
|
||||
static idVec3 FindNearestPerpendicular(
|
||||
const idVec3& input,
|
||||
const idVec3& up,
|
||||
const idVec3& hint
|
||||
);
|
||||
|
||||
static float AreaOfTriangle(
|
||||
const idVec3& a,
|
||||
const idVec3& b,
|
||||
const idVec3& c
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "screenrect.h"
|
||||
|
||||
void idScreenRect::Clear() {
|
||||
x1 = 32000;
|
||||
y1 = 32000;
|
||||
x2 = -32000;
|
||||
y2 = -32000;
|
||||
}
|
||||
|
||||
void idScreenRect::Zero() {
|
||||
x1 = 0;
|
||||
y1 = 0;
|
||||
x2 = 0;
|
||||
y2 = 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
// tungsten.exe.h type 12913. Unlike Doom 3 BFG's idScreenRect, this idTech 5
|
||||
// build uses four 32-bit coordinates and does not store depth bounds here.
|
||||
class idScreenRect {
|
||||
public:
|
||||
int x1;
|
||||
int y1;
|
||||
int x2;
|
||||
int y2;
|
||||
|
||||
void Clear();
|
||||
void Zero();
|
||||
|
||||
bool IsEmpty() const {
|
||||
return x1 > x2 || y1 > y2;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(idScreenRect) == 16, "Recovered idScreenRect layout changed");
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "crc16.h"
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
|
||||
namespace {
|
||||
|
||||
using CrcTable = std::array<std::uint16_t, 256>;
|
||||
|
||||
CrcTable BuildCrcTable() {
|
||||
CrcTable table = {};
|
||||
|
||||
for (std::size_t index = 0; index < table.size(); ++index) {
|
||||
std::uint16_t value = static_cast<std::uint16_t>(index << 8);
|
||||
for (int bit = 0; bit < 8; ++bit) {
|
||||
value = (value & 0x8000u) != 0
|
||||
? static_cast<std::uint16_t>((value << 1) ^ 0x1021u)
|
||||
: static_cast<std::uint16_t>(value << 1);
|
||||
}
|
||||
table[index] = value;
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
const CrcTable& GetCrcTable() {
|
||||
static const CrcTable table = BuildCrcTable();
|
||||
return table;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void CRC16_UpdateChecksum(
|
||||
std::uint16_t& crcValue,
|
||||
const void* data,
|
||||
const int length
|
||||
) {
|
||||
assert(length >= 0);
|
||||
assert(data != nullptr || length == 0);
|
||||
|
||||
if (length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const CrcTable& table = GetCrcTable();
|
||||
const auto* bytes = static_cast<const std::uint8_t*>(data);
|
||||
|
||||
for (int index = 0; index < length; ++index) {
|
||||
const std::uint8_t tableIndex = static_cast<std::uint8_t>(
|
||||
(crcValue >> 8) ^ bytes[index]
|
||||
);
|
||||
crcValue = static_cast<std::uint16_t>(
|
||||
table[tableIndex] ^ static_cast<std::uint16_t>(crcValue << 8)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// Recovered from ?CRC16_UpdateChecksum@@YAXAAGPBXH@Z at 0x82F2BE00.
|
||||
// The reference parameter and const void pointer match the decorated PDB name;
|
||||
// Hex-Rays displayed the reference as a pointer in the raw 360 dump.
|
||||
void CRC16_UpdateChecksum(
|
||||
std::uint16_t& crcValue,
|
||||
const void* data,
|
||||
int length
|
||||
);
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
#include "murmur.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::uint32_t MURMUR_MULTIPLIER = 0x5BD1E995u;
|
||||
|
||||
std::uint32_t ReadLittleEndian32(const std::uint8_t* data) {
|
||||
return static_cast<std::uint32_t>(data[0])
|
||||
| (static_cast<std::uint32_t>(data[1]) << 8)
|
||||
| (static_cast<std::uint32_t>(data[2]) << 16)
|
||||
| (static_cast<std::uint32_t>(data[3]) << 24);
|
||||
}
|
||||
|
||||
std::uint32_t ReadBigEndian32(const std::uint8_t* data) {
|
||||
return (static_cast<std::uint32_t>(data[0]) << 24)
|
||||
| (static_cast<std::uint32_t>(data[1]) << 16)
|
||||
| (static_cast<std::uint32_t>(data[2]) << 8)
|
||||
| static_cast<std::uint32_t>(data[3]);
|
||||
}
|
||||
|
||||
std::uint32_t MixWord(std::uint32_t value) {
|
||||
value *= MURMUR_MULTIPLIER;
|
||||
value ^= value >> 24;
|
||||
value *= MURMUR_MULTIPLIER;
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::uint32_t MurMur32_HashData(
|
||||
const void* key,
|
||||
const int length,
|
||||
const std::uint32_t seed
|
||||
) {
|
||||
assert(length >= 0);
|
||||
assert(key != nullptr || length == 0);
|
||||
|
||||
if (length < 0 || (key == nullptr && length != 0)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto* data = static_cast<const std::uint8_t*>(key);
|
||||
int remaining = length;
|
||||
std::uint32_t hash = seed ^ static_cast<std::uint32_t>(length);
|
||||
|
||||
while (remaining >= 4) {
|
||||
const std::uint32_t word = MixWord(ReadLittleEndian32(data));
|
||||
hash = (hash * MURMUR_MULTIPLIER) ^ word;
|
||||
data += 4;
|
||||
remaining -= 4;
|
||||
}
|
||||
|
||||
switch (remaining) {
|
||||
case 3:
|
||||
hash ^= static_cast<std::uint32_t>(data[2]) << 16;
|
||||
// Fall through.
|
||||
case 2:
|
||||
hash ^= static_cast<std::uint32_t>(data[1]) << 8;
|
||||
// Fall through.
|
||||
case 1:
|
||||
hash ^= data[0];
|
||||
hash *= MURMUR_MULTIPLIER;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
hash ^= hash >> 13;
|
||||
hash *= MURMUR_MULTIPLIER;
|
||||
hash ^= hash >> 15;
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::uint64_t MurMur64_HashData(
|
||||
const void* key,
|
||||
const int length,
|
||||
const std::uint32_t seed
|
||||
) {
|
||||
assert(length >= 0);
|
||||
assert(key != nullptr || length == 0);
|
||||
|
||||
if (length < 0 || (key == nullptr && length != 0)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto* data = static_cast<const std::uint8_t*>(key);
|
||||
int remaining = length;
|
||||
std::uint32_t hash1 = seed ^ static_cast<std::uint32_t>(length);
|
||||
std::uint32_t hash2 = 0;
|
||||
|
||||
// The recovered 360 function loads full words directly on big-endian PPC,
|
||||
// but constructs its one-to-three-byte tail explicitly. Preserve that byte
|
||||
// behavior so hashes continue to match the supplied Xbox resource data.
|
||||
while (remaining >= 8) {
|
||||
hash1 = (hash1 * MURMUR_MULTIPLIER)
|
||||
^ MixWord(ReadBigEndian32(data));
|
||||
hash2 = (hash2 * MURMUR_MULTIPLIER)
|
||||
^ MixWord(ReadBigEndian32(data + 4));
|
||||
data += 8;
|
||||
remaining -= 8;
|
||||
}
|
||||
|
||||
if (remaining >= 4) {
|
||||
hash1 = (hash1 * MURMUR_MULTIPLIER)
|
||||
^ MixWord(ReadBigEndian32(data));
|
||||
data += 4;
|
||||
remaining -= 4;
|
||||
}
|
||||
|
||||
switch (remaining) {
|
||||
case 3:
|
||||
hash2 ^= static_cast<std::uint32_t>(data[2]) << 16;
|
||||
// Fall through.
|
||||
case 2:
|
||||
hash2 ^= static_cast<std::uint32_t>(data[1]) << 8;
|
||||
// Fall through.
|
||||
case 1:
|
||||
hash2 ^= data[0];
|
||||
hash2 *= MURMUR_MULTIPLIER;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
hash1 ^= hash2 >> 18;
|
||||
hash1 *= MURMUR_MULTIPLIER;
|
||||
hash2 ^= hash1 >> 22;
|
||||
hash2 *= MURMUR_MULTIPLIER;
|
||||
hash1 ^= hash2 >> 17;
|
||||
hash1 *= MURMUR_MULTIPLIER;
|
||||
hash2 ^= hash1 >> 19;
|
||||
hash2 *= MURMUR_MULTIPLIER;
|
||||
|
||||
return (static_cast<std::uint64_t>(hash1) << 32) | hash2;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// PDB signatures:
|
||||
// ?MurMur32_HashData@@YAIPBXHI@Z
|
||||
// ?MurMur64_HashData@@YA_KPBXHI@Z
|
||||
std::uint32_t MurMur32_HashData(
|
||||
const void* key,
|
||||
int length,
|
||||
std::uint32_t seed
|
||||
);
|
||||
|
||||
std::uint64_t MurMur64_HashData(
|
||||
const void* key,
|
||||
int length,
|
||||
std::uint32_t seed
|
||||
);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
template<typename valueType, typename invalidType>
|
||||
class idIndex {
|
||||
public:
|
||||
idIndex()
|
||||
: value(static_cast<valueType>(-1)) {
|
||||
}
|
||||
|
||||
explicit idIndex(const valueType index)
|
||||
: value(index) {
|
||||
}
|
||||
|
||||
bool IsValid() const {
|
||||
return value != static_cast<valueType>(-1);
|
||||
}
|
||||
|
||||
void Invalidate() {
|
||||
value = static_cast<valueType>(-1);
|
||||
}
|
||||
|
||||
valueType Get() const {
|
||||
return value;
|
||||
}
|
||||
|
||||
operator valueType() const {
|
||||
return value;
|
||||
}
|
||||
|
||||
bool operator==(const idIndex& other) const { return value == other.value; }
|
||||
bool operator!=(const idIndex& other) const { return value != other.value; }
|
||||
bool operator<(const idIndex& other) const { return value < other.value; }
|
||||
|
||||
private:
|
||||
valueType value;
|
||||
};
|
||||
|
||||
enum class idRecoveredInvalidIndex : int { invalid = -1 };
|
||||
static_assert(sizeof(idIndex<short, idRecoveredInvalidIndex>) == sizeof(short),
|
||||
"Recovered idIndex ABI changed");
|
||||
@@ -0,0 +1,200 @@
|
||||
#include "lib_print.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
struct warningInfo_t { const char* type; const char* name; };
|
||||
|
||||
std::recursive_mutex printMutex;
|
||||
idPrintListener* listeners = nullptr;
|
||||
std::array<warningInfo_t, 32> warningInfo{};
|
||||
int numWarningInfo = 0;
|
||||
idLibPrint::fatalErrorHandler_t fatalErrorHandler = nullptr;
|
||||
const std::thread::id mainThread = std::this_thread::get_id();
|
||||
|
||||
std::string RemoveColors(const std::string& source) {
|
||||
std::string result;
|
||||
result.reserve(source.size());
|
||||
for (std::size_t index = 0; index < source.size(); ++index) {
|
||||
if (source[index] == '^' && index + 1 < source.size()
|
||||
&& source[index + 1] >= '0' && source[index + 1] <= '9') {
|
||||
++index;
|
||||
} else {
|
||||
result.push_back(source[index]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string Format(const char* format, va_list args) {
|
||||
char buffer[4096] = {};
|
||||
const int amount = std::vsnprintf(buffer, sizeof(buffer),
|
||||
format == nullptr ? "" : format, args);
|
||||
if (amount < 0) {
|
||||
buffer[sizeof(buffer) - 2] = '\n';
|
||||
buffer[sizeof(buffer) - 1] = '\0';
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
const char* Prefix(const printSeverity_t severity) {
|
||||
switch (severity) {
|
||||
case SEV_WARNING: return "^3WARNING: ^1";
|
||||
case SEV_ERROR: return "^3ERROR: ^1";
|
||||
case SEV_FATAL: return "^3FATAL ERROR: ^1";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
void ThrowMessage(const printSeverity_t severity, const std::string& message) {
|
||||
throw idRecoveredPrintException(RemoveColors(message).c_str(),
|
||||
severity == SEV_FATAL);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
idPrintListener::idPrintListener()
|
||||
: next(nullptr), wantColor(true), threadSafe(false),
|
||||
minSeverity(SEV_PRINT) {}
|
||||
|
||||
idPrintListener::~idPrintListener() { UnRegisterPrintListener(); }
|
||||
|
||||
void idPrintListener::RegisterPrintListener() {
|
||||
idLibPrint::RegisterPrintListener(this);
|
||||
}
|
||||
|
||||
void idPrintListener::UnRegisterPrintListener() {
|
||||
idLibPrint::UnRegisterPrintListener(this);
|
||||
}
|
||||
|
||||
bool idLibPrint::PushWarningInfo(const char* type, const char* name) {
|
||||
std::lock_guard<std::recursive_mutex> lock(printMutex);
|
||||
if (numWarningInfo >= static_cast<int>(warningInfo.size())) return false;
|
||||
warningInfo[static_cast<std::size_t>(numWarningInfo++)] = {type, name};
|
||||
return true;
|
||||
}
|
||||
|
||||
void idLibPrint::PopWarningInfo() {
|
||||
std::lock_guard<std::recursive_mutex> lock(printMutex);
|
||||
if (numWarningInfo > 0) --numWarningInfo;
|
||||
}
|
||||
|
||||
void idLibPrint::RegisterFatalErrorHandler(fatalErrorHandler_t handler) {
|
||||
std::lock_guard<std::recursive_mutex> lock(printMutex);
|
||||
fatalErrorHandler = handler;
|
||||
}
|
||||
|
||||
void idLibPrint::RegisterPrintListener(idPrintListener* listener) {
|
||||
if (listener == nullptr) return;
|
||||
std::lock_guard<std::recursive_mutex> lock(printMutex);
|
||||
for (idPrintListener* current = listeners; current != nullptr;
|
||||
current = current->next) {
|
||||
if (current == listener) return;
|
||||
}
|
||||
listener->next = listeners;
|
||||
listeners = listener;
|
||||
}
|
||||
|
||||
void idLibPrint::UnRegisterPrintListener(idPrintListener* listener) {
|
||||
if (listener == nullptr) return;
|
||||
std::lock_guard<std::recursive_mutex> lock(printMutex);
|
||||
idPrintListener** link = &listeners;
|
||||
while (*link != nullptr && *link != listener) link = &(*link)->next;
|
||||
if (*link == listener) {
|
||||
*link = listener->next;
|
||||
listener->next = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void idLibPrint::Dispatch(const printSeverity_t severity, const char* format,
|
||||
va_list args) {
|
||||
va_list copy;
|
||||
va_copy(copy, args);
|
||||
std::string message = Prefix(severity);
|
||||
message += Format(format, copy);
|
||||
va_end(copy);
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(printMutex);
|
||||
if (severity >= SEV_WARNING) {
|
||||
for (int index = numWarningInfo - 1; index >= 0; --index) {
|
||||
message += index == numWarningInfo - 1
|
||||
? " ^7while loading " : " ^8from ";
|
||||
message += warningInfo[static_cast<std::size_t>(index)].type == nullptr
|
||||
? "" : warningInfo[static_cast<std::size_t>(index)].type;
|
||||
message.push_back(' ');
|
||||
message += warningInfo[static_cast<std::size_t>(index)].name == nullptr
|
||||
? "" : warningInfo[static_cast<std::size_t>(index)].name;
|
||||
}
|
||||
message.push_back('\n');
|
||||
}
|
||||
|
||||
const bool onMainThread = std::this_thread::get_id() == mainThread;
|
||||
bool delivered = false;
|
||||
for (idPrintListener* listener = listeners; listener != nullptr;
|
||||
listener = listener->next) {
|
||||
if (listener->wantColor && severity >= listener->minSeverity
|
||||
&& (onMainThread || listener->threadSafe)) {
|
||||
listener->Print(message.c_str());
|
||||
delivered = true;
|
||||
}
|
||||
}
|
||||
const std::string plain = RemoveColors(message);
|
||||
for (idPrintListener* listener = listeners; listener != nullptr;
|
||||
listener = listener->next) {
|
||||
if (!listener->wantColor && severity >= listener->minSeverity
|
||||
&& (onMainThread || listener->threadSafe)) {
|
||||
listener->Print(plain.c_str());
|
||||
delivered = true;
|
||||
}
|
||||
}
|
||||
if (!delivered) std::fputs(plain.c_str(), stderr);
|
||||
|
||||
if (severity == SEV_FATAL && fatalErrorHandler != nullptr) {
|
||||
fatalErrorHandler(plain.c_str());
|
||||
}
|
||||
if (severity >= SEV_ERROR) ThrowMessage(severity, message);
|
||||
}
|
||||
|
||||
void idLibPrint::Debugf(const char* format, ...) {
|
||||
va_list args; va_start(args, format); Dispatch(SEV_DEBUG, format, args); va_end(args);
|
||||
}
|
||||
|
||||
void idLibPrint::Printf(const char* format, ...) {
|
||||
va_list args; va_start(args, format); Dispatch(SEV_PRINT, format, args); va_end(args);
|
||||
}
|
||||
|
||||
void idLibPrint::PrintfIf(const bool condition, const char* format, ...) {
|
||||
if (!condition) return;
|
||||
va_list args; va_start(args, format); Dispatch(SEV_PRINT, format, args); va_end(args);
|
||||
}
|
||||
|
||||
void idLibPrint::VPrintf(const char* format, va_list args) {
|
||||
Dispatch(SEV_PRINT, format, args);
|
||||
}
|
||||
|
||||
void idLibPrint::Warning(const char* format, ...) {
|
||||
va_list args; va_start(args, format); Dispatch(SEV_WARNING, format, args); va_end(args);
|
||||
}
|
||||
|
||||
void idLibPrint::WarningIf(const bool condition, const char* format, ...) {
|
||||
if (!condition) return;
|
||||
va_list args; va_start(args, format); Dispatch(SEV_WARNING, format, args); va_end(args);
|
||||
}
|
||||
|
||||
void idLibPrint::Error(const char* format, ...) {
|
||||
va_list args; va_start(args, format); Dispatch(SEV_ERROR, format, args); va_end(args);
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
void idLibPrint::FatalError(const char* format, ...) {
|
||||
va_list args; va_start(args, format); Dispatch(SEV_FATAL, format, args); va_end(args);
|
||||
std::terminate();
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdarg>
|
||||
#include <stdexcept>
|
||||
|
||||
enum printSeverity_t {
|
||||
SEV_DEBUG = 0,
|
||||
SEV_PRINT = 1,
|
||||
SEV_WARNING = 2,
|
||||
SEV_ERROR = 3,
|
||||
SEV_FATAL = 4
|
||||
};
|
||||
|
||||
class idPrintListener {
|
||||
public:
|
||||
idPrintListener();
|
||||
virtual ~idPrintListener();
|
||||
virtual void Print(const char* text) = 0;
|
||||
|
||||
void RegisterPrintListener();
|
||||
void UnRegisterPrintListener();
|
||||
|
||||
idPrintListener* next;
|
||||
bool wantColor;
|
||||
bool threadSafe;
|
||||
printSeverity_t minSeverity;
|
||||
};
|
||||
|
||||
class idRecoveredPrintException : public std::runtime_error {
|
||||
public:
|
||||
idRecoveredPrintException(const char* message, bool fatal)
|
||||
: std::runtime_error(message == nullptr ? "" : message),
|
||||
fatalError(fatal) {}
|
||||
bool IsFatal() const { return fatalError; }
|
||||
private:
|
||||
bool fatalError;
|
||||
};
|
||||
|
||||
// PC-side extension for the listener/context facilities added after the BFG
|
||||
// idLib interface. The six common Printf/Warning/Error entry points continue
|
||||
// to be supplied by the BFG baseline until the engine-wide Lib.h is replaced.
|
||||
class idLibPrint {
|
||||
public:
|
||||
using fatalErrorHandler_t = void (*)(const char*);
|
||||
|
||||
static bool PushWarningInfo(const char* type, const char* name);
|
||||
static void PopWarningInfo();
|
||||
static void RegisterFatalErrorHandler(fatalErrorHandler_t handler);
|
||||
|
||||
static void Debugf(const char* format, ...);
|
||||
static void Printf(const char* format, ...);
|
||||
static void PrintfIf(bool condition, const char* format, ...);
|
||||
static void VPrintf(const char* format, va_list args);
|
||||
static void Warning(const char* format, ...);
|
||||
static void WarningIf(bool condition, const char* format, ...);
|
||||
[[noreturn]] static void Error(const char* format, ...);
|
||||
[[noreturn]] static void FatalError(const char* format, ...);
|
||||
|
||||
static void Dispatch(printSeverity_t severity, const char* format,
|
||||
va_list args);
|
||||
static void RegisterPrintListener(idPrintListener* listener);
|
||||
static void UnRegisterPrintListener(idPrintListener* listener);
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idPrintListener) == 16,
|
||||
"Recovered idPrintListener layout changed");
|
||||
#endif
|
||||
@@ -0,0 +1,321 @@
|
||||
#include "lookuptable.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
|
||||
int PositiveModulo(const int value, const int modulus) {
|
||||
const int remainder = value % modulus;
|
||||
return remainder < 0 ? remainder + modulus : remainder;
|
||||
}
|
||||
|
||||
int FloorDivide(const int value, const int divisor) {
|
||||
int quotient = value / divisor;
|
||||
if (value < 0 && value % divisor != 0) {
|
||||
--quotient;
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void idCatmullRomSpline<float>::Sort() {
|
||||
for (int end = numKnots - 1; end > 0; --end) {
|
||||
int maximumIndex = 0;
|
||||
for (int index = 1; index <= end; ++index) {
|
||||
if (times[index] > times[maximumIndex]) {
|
||||
maximumIndex = index;
|
||||
}
|
||||
}
|
||||
std::swap(times[maximumIndex], times[end]);
|
||||
std::swap(values[maximumIndex], values[end]);
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
|
||||
void idCatmullRomSpline<float>::Normalize(const float totalTime) {
|
||||
if (numKnots < 1 || times[numKnots - 1] == 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float scale = totalTime / times[numKnots - 1];
|
||||
for (int index = 0; index < numKnots; ++index) {
|
||||
times[index] *= scale;
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
|
||||
float idCatmullRomSpline<float>::ClampedTime(const float time) const {
|
||||
if (boundaryType == CLAMPED) {
|
||||
if (time < times[0]) {
|
||||
return times[0];
|
||||
}
|
||||
if (time >= times[numKnots - 1]) {
|
||||
return times[numKnots - 1];
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
if (boundaryType == CLOSED) {
|
||||
const float period = times[numKnots - 1] + closeTime;
|
||||
if (period == 0.0f) {
|
||||
return times[0];
|
||||
}
|
||||
return time - std::floor(time / period) * period;
|
||||
}
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
int idCatmullRomSpline<float>::IndexForTime(
|
||||
const float time,
|
||||
const bool fastSearch
|
||||
) const {
|
||||
if (fastSearch && currentIndex >= 0 && currentIndex <= numKnots) {
|
||||
if (currentIndex == 0) {
|
||||
if (time <= times[0]) {
|
||||
return 0;
|
||||
}
|
||||
} else if (currentIndex == numKnots) {
|
||||
if (time > times[numKnots - 1]) {
|
||||
return currentIndex;
|
||||
}
|
||||
} else if (time > times[currentIndex - 1]
|
||||
&& time <= times[currentIndex]) {
|
||||
return currentIndex;
|
||||
} else if (currentIndex + 1 <= numKnots
|
||||
&& time > times[currentIndex]
|
||||
&& (currentIndex + 1 == numKnots
|
||||
|| time <= times[currentIndex + 1])) {
|
||||
++currentIndex;
|
||||
return currentIndex;
|
||||
}
|
||||
}
|
||||
|
||||
int low = 0;
|
||||
int high = numKnots;
|
||||
while (low < high) {
|
||||
const int middle = low + (high - low) / 2;
|
||||
if (time <= times[middle]) {
|
||||
high = middle;
|
||||
} else {
|
||||
low = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (fastSearch) {
|
||||
currentIndex = low;
|
||||
}
|
||||
return low;
|
||||
}
|
||||
|
||||
float idCatmullRomSpline<float>::TimeForIndex(const int index) const {
|
||||
const int lastIndex = numKnots - 1;
|
||||
if (index >= 0 && index <= lastIndex) {
|
||||
return times[index];
|
||||
}
|
||||
|
||||
if (boundaryType == CLOSED) {
|
||||
const float period = times[lastIndex] + closeTime;
|
||||
const int cycle = FloorDivide(index, numKnots);
|
||||
return static_cast<float>(cycle) * period
|
||||
+ times[PositiveModulo(index, numKnots)];
|
||||
}
|
||||
|
||||
if (index < 0) {
|
||||
return times[0]
|
||||
+ static_cast<float>(index) * (times[1] - times[0]);
|
||||
}
|
||||
return times[lastIndex]
|
||||
+ static_cast<float>(index - lastIndex)
|
||||
* (times[lastIndex] - times[lastIndex - 1]);
|
||||
}
|
||||
|
||||
float idCatmullRomSpline<float>::ValueForIndex(const int index) const {
|
||||
const int lastIndex = numKnots - 1;
|
||||
if (index >= 0 && index <= lastIndex) {
|
||||
return values[index];
|
||||
}
|
||||
|
||||
if (boundaryType == CLOSED) {
|
||||
return values[PositiveModulo(index, numKnots)];
|
||||
}
|
||||
if (boundaryType == CLAMPED) {
|
||||
return index < 0 ? values[0] : values[lastIndex];
|
||||
}
|
||||
|
||||
if (index < 0) {
|
||||
return values[0]
|
||||
+ static_cast<float>(index) * (values[1] - values[0]);
|
||||
}
|
||||
return values[lastIndex]
|
||||
+ static_cast<float>(index - lastIndex)
|
||||
* (values[lastIndex] - values[lastIndex - 1]);
|
||||
}
|
||||
|
||||
void idCatmullRomSpline<float>::Basis(
|
||||
const int index,
|
||||
const float time,
|
||||
float basisValues[4]
|
||||
) const {
|
||||
const float intervalStart = TimeForIndex(index);
|
||||
const float intervalEnd = TimeForIndex(index + 1);
|
||||
const float intervalLength = intervalEnd - intervalStart;
|
||||
const float s = intervalLength == 0.0f
|
||||
? 0.0f
|
||||
: (time - intervalStart) / intervalLength;
|
||||
|
||||
basisValues[0] = 0.5f * (((2.0f - s) * s - 1.0f) * s);
|
||||
basisValues[1] = 0.5f * ((((3.0f * s - 5.0f) * s) * s) + 2.0f);
|
||||
basisValues[2] = 0.5f * ((((4.0f - 3.0f * s) * s + 1.0f) * s));
|
||||
basisValues[3] = 0.5f * ((s - 1.0f) * s * s);
|
||||
}
|
||||
|
||||
float idCatmullRomSpline<float>::GetCurrentValue(
|
||||
const float time,
|
||||
const bool fastSearch
|
||||
) const {
|
||||
if (numKnots == 1) {
|
||||
return values[0];
|
||||
}
|
||||
|
||||
const float adjustedTime = ClampedTime(time);
|
||||
const int upperIndex = IndexForTime(adjustedTime, fastSearch);
|
||||
const int basisIndex = upperIndex - 1;
|
||||
float basisValues[4];
|
||||
Basis(basisIndex, adjustedTime, basisValues);
|
||||
|
||||
float result = 0.0f;
|
||||
for (int index = 0; index < 4; ++index) {
|
||||
result += ValueForIndex(basisIndex - 1 + index)
|
||||
* basisValues[index];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
idLookupTable::idLookupTable()
|
||||
: clamp(false)
|
||||
, snap(false)
|
||||
, spline(false)
|
||||
, minimum(0.0f)
|
||||
, maximum(1.0f)
|
||||
, values{} {
|
||||
values.numKnots = 0;
|
||||
values.currentIndex = -1;
|
||||
values.changed = true;
|
||||
values.boundaryType = idCatmullRomSpline<float>::CLOSED;
|
||||
values.closeTime = 1.0f;
|
||||
}
|
||||
|
||||
void idLookupTable::SetSnap(const bool enabled) {
|
||||
snap = enabled;
|
||||
if (spline && enabled) {
|
||||
spline = false;
|
||||
}
|
||||
}
|
||||
|
||||
void idLookupTable::SetSpline(const bool enabled) {
|
||||
spline = enabled;
|
||||
if (enabled && snap) {
|
||||
snap = false;
|
||||
}
|
||||
}
|
||||
|
||||
void idLookupTable::Finalize() {
|
||||
values.Sort();
|
||||
if (values.numKnots > 1 && values.times[values.numKnots - 1] > 1.0f) {
|
||||
const float totalTime = clamp
|
||||
? static_cast<float>(values.numKnots - 1)
|
||||
: 1.0f;
|
||||
values.Normalize(totalTime);
|
||||
}
|
||||
if (values.numKnots > 0) {
|
||||
values.closeTime = 1.0f - values.times[values.numKnots - 1];
|
||||
} else {
|
||||
values.closeTime = 1.0f;
|
||||
}
|
||||
values.changed = true;
|
||||
}
|
||||
|
||||
void idLookupTable::SetClamp(const bool enabled) {
|
||||
clamp = enabled;
|
||||
values.changed = true;
|
||||
values.boundaryType = enabled
|
||||
? idCatmullRomSpline<float>::CLAMPED
|
||||
: idCatmullRomSpline<float>::CLOSED;
|
||||
}
|
||||
|
||||
void idLookupTable::AddValue(const float time, const float value) {
|
||||
if (values.numKnots >= 64) {
|
||||
return;
|
||||
}
|
||||
|
||||
values.times[values.numKnots] = time;
|
||||
values.values[values.numKnots] = value;
|
||||
++values.numKnots;
|
||||
values.changed = true;
|
||||
}
|
||||
|
||||
void idLookupTable::Clear() {
|
||||
values.numKnots = 0;
|
||||
values.currentIndex = -1;
|
||||
values.changed = true;
|
||||
minimum = 0.0f;
|
||||
maximum = 1.0f;
|
||||
clamp = false;
|
||||
snap = false;
|
||||
spline = false;
|
||||
values.boundaryType = idCatmullRomSpline<float>::CLOSED;
|
||||
values.closeTime = 1.0f;
|
||||
}
|
||||
|
||||
float idLookupTable::TableLookupNormalized(
|
||||
const float time,
|
||||
const bool fastSearch
|
||||
) const {
|
||||
if (values.numKnots == 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
if (values.numKnots == 1) {
|
||||
return values.values[0];
|
||||
}
|
||||
if (spline) {
|
||||
return values.GetCurrentValue(time, fastSearch);
|
||||
}
|
||||
|
||||
if (clamp) {
|
||||
if (time <= values.times[0]) {
|
||||
return values.values[0];
|
||||
}
|
||||
if (time >= values.times[values.numKnots - 1]) {
|
||||
return values.values[values.numKnots - 1];
|
||||
}
|
||||
}
|
||||
|
||||
const float adjustedTime = values.ClampedTime(time);
|
||||
const int upperIndex = values.IndexForTime(adjustedTime, fastSearch);
|
||||
if (snap) {
|
||||
return values.ValueForIndex(upperIndex - 1);
|
||||
}
|
||||
|
||||
const float lowerTime = values.TimeForIndex(upperIndex - 1);
|
||||
const float upperTime = values.TimeForIndex(upperIndex);
|
||||
if (lowerTime == upperTime) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const float lowerValue = values.ValueForIndex(upperIndex - 1);
|
||||
const float upperValue = values.ValueForIndex(upperIndex);
|
||||
const float fraction = (adjustedTime - lowerTime)
|
||||
/ (upperTime - lowerTime);
|
||||
return lowerValue + (upperValue - lowerValue) * fraction;
|
||||
}
|
||||
|
||||
float idLookupTable::TableLookup(
|
||||
const float time,
|
||||
const bool fastSearch
|
||||
) const {
|
||||
return minimum + (maximum - minimum)
|
||||
* TableLookupNormalized(time, fastSearch);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
template<typename Type>
|
||||
class idCatmullRomSpline;
|
||||
|
||||
template<>
|
||||
class idCatmullRomSpline<float> {
|
||||
public:
|
||||
enum boundary_t {
|
||||
FREE = 0,
|
||||
CLAMPED = 1,
|
||||
CLOSED = 2
|
||||
};
|
||||
|
||||
float times[64];
|
||||
float values[64];
|
||||
int numKnots;
|
||||
mutable int currentIndex;
|
||||
bool changed;
|
||||
boundary_t boundaryType;
|
||||
float closeTime;
|
||||
|
||||
void Sort();
|
||||
void Normalize(float totalTime);
|
||||
float ClampedTime(float time) const;
|
||||
int IndexForTime(float time, bool fastSearch) const;
|
||||
float TimeForIndex(int index) const;
|
||||
float ValueForIndex(int index) const;
|
||||
void Basis(int index, float time, float basisValues[4]) const;
|
||||
float GetCurrentValue(float time, bool fastSearch) const;
|
||||
};
|
||||
|
||||
static_assert(
|
||||
sizeof(idCatmullRomSpline<float>) == 532,
|
||||
"Recovered idCatmullRomSpline<float> layout changed"
|
||||
);
|
||||
|
||||
class idLookupTable {
|
||||
public:
|
||||
idLookupTable();
|
||||
|
||||
void SetSnap(bool enabled);
|
||||
void SetSpline(bool enabled);
|
||||
void Finalize();
|
||||
void SetClamp(bool enabled);
|
||||
void AddValue(float time, float value);
|
||||
void Clear();
|
||||
|
||||
float TableLookupNormalized(float time, bool fastSearch) const;
|
||||
float TableLookup(float time, bool fastSearch) const;
|
||||
|
||||
private:
|
||||
bool clamp;
|
||||
bool snap;
|
||||
bool spline;
|
||||
float minimum;
|
||||
float maximum;
|
||||
idCatmullRomSpline<float> values;
|
||||
};
|
||||
|
||||
static_assert(sizeof(idLookupTable) == 544, "idLookupTable ABI changed");
|
||||
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class idBoundedIntBase {
|
||||
public:
|
||||
virtual ~idBoundedIntBase() = default;
|
||||
virtual void SetValue(int value) = 0;
|
||||
virtual int GetValue() const = 0;
|
||||
};
|
||||
|
||||
template<int MIN_VALUE, int MAX_VALUE>
|
||||
class idBoundedInt final : public idBoundedIntBase {
|
||||
public:
|
||||
explicit idBoundedInt(const int initialValue = MIN_VALUE)
|
||||
: value(MIN_VALUE) {
|
||||
SetValue(initialValue);
|
||||
}
|
||||
|
||||
void SetValue(const int newValue) override {
|
||||
value = newValue < MIN_VALUE ? MIN_VALUE
|
||||
: (newValue > MAX_VALUE ? MAX_VALUE : newValue);
|
||||
}
|
||||
|
||||
int GetValue() const override {
|
||||
return value;
|
||||
}
|
||||
|
||||
operator int() const {
|
||||
return value;
|
||||
}
|
||||
|
||||
private:
|
||||
int value;
|
||||
};
|
||||
|
||||
class idBoundedFloatBase {
|
||||
public:
|
||||
virtual ~idBoundedFloatBase() = default;
|
||||
virtual void SetValue(float value) = 0;
|
||||
virtual float GetValue() const = 0;
|
||||
};
|
||||
|
||||
// The original uses four integral template arguments so floating-point bounds
|
||||
// remain legal in the C++03-era source. Only <0,0,1,0> occurs in tungsten;
|
||||
// the second and fourth arguments represent the fractional decimal component.
|
||||
template<int MIN_WHOLE, int MIN_FRACTION, int MAX_WHOLE, int MAX_FRACTION>
|
||||
class idBoundedFloat final : public idBoundedFloatBase {
|
||||
public:
|
||||
explicit idBoundedFloat(const float initialValue = Minimum())
|
||||
: value(Minimum()) {
|
||||
SetValue(initialValue);
|
||||
}
|
||||
|
||||
void SetValue(const float newValue) override {
|
||||
value = newValue < Minimum() ? Minimum()
|
||||
: (newValue > Maximum() ? Maximum() : newValue);
|
||||
}
|
||||
|
||||
float GetValue() const override {
|
||||
return value;
|
||||
}
|
||||
|
||||
operator float() const {
|
||||
return value;
|
||||
}
|
||||
|
||||
private:
|
||||
float value;
|
||||
|
||||
static constexpr float Fraction(const int digits) {
|
||||
return static_cast<float>(digits) / 1000.0f;
|
||||
}
|
||||
|
||||
static constexpr float Minimum() {
|
||||
return static_cast<float>(MIN_WHOLE) + Fraction(MIN_FRACTION);
|
||||
}
|
||||
|
||||
static constexpr float Maximum() {
|
||||
return static_cast<float>(MAX_WHOLE) + Fraction(MAX_FRACTION);
|
||||
}
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idBoundedInt<0, 4>) == 8,
|
||||
"Recovered idBoundedInt ABI changed");
|
||||
static_assert(sizeof(idBoundedFloat<0, 0, 1, 0>) == 8,
|
||||
"Recovered idBoundedFloat ABI changed");
|
||||
#endif
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "decay.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
idParametricDecay::idParametricDecay()
|
||||
: delta(0.0f)
|
||||
, linear(0.0f)
|
||||
, t0(0.0f)
|
||||
, tdelta(0.0f)
|
||||
, lambda(0.0f) {
|
||||
}
|
||||
|
||||
void idParametricDecay::Init(
|
||||
const float newDelta,
|
||||
const float newLinear,
|
||||
const float newT0,
|
||||
const float newTDelta,
|
||||
const float newLambda
|
||||
) {
|
||||
delta = newDelta;
|
||||
linear = newLinear;
|
||||
t0 = newT0;
|
||||
tdelta = newTDelta;
|
||||
lambda = newLambda;
|
||||
}
|
||||
|
||||
void idParametricDecay::SetTZero(const float newT0) {
|
||||
t0 = newT0;
|
||||
}
|
||||
|
||||
void idParametricDecay::SetDelta(const float newDelta) {
|
||||
delta = newDelta;
|
||||
}
|
||||
|
||||
float idParametricDecay::Evaluate(const float t) const {
|
||||
if (t < t0) {
|
||||
return delta;
|
||||
}
|
||||
|
||||
const float elapsed = t - t0;
|
||||
if (elapsed > tdelta) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const float normalizedTime = elapsed / tdelta;
|
||||
const float exponential = std::pow(
|
||||
0.5f,
|
||||
elapsed / (lambda * tdelta)
|
||||
);
|
||||
return ((1.0f - linear) * exponential
|
||||
+ (1.0f - normalizedTime) * linear) * delta;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
// tungsten.exe.h type 12849.
|
||||
class idParametricDecay {
|
||||
public:
|
||||
idParametricDecay();
|
||||
|
||||
void Init(float delta, float linear, float t0, float tdelta, float lambda);
|
||||
void SetTZero(float t0);
|
||||
void SetDelta(float delta);
|
||||
float Evaluate(float t) const;
|
||||
|
||||
private:
|
||||
float delta;
|
||||
float linear;
|
||||
float t0;
|
||||
float tdelta;
|
||||
float lambda;
|
||||
};
|
||||
|
||||
static_assert(sizeof(idParametricDecay) == 20,
|
||||
"Recovered idParametricDecay layout changed");
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
|
||||
class idFader {
|
||||
public:
|
||||
enum type_t {
|
||||
FADE_LINEAR = 0,
|
||||
FADE_SINE = 1,
|
||||
FADE_INVERSE_SINE = 2
|
||||
};
|
||||
|
||||
idFader(const type_t fadeType = FADE_LINEAR, const float initialValue = 0.0f)
|
||||
: type(fadeType), startTime(0), duration(0),
|
||||
startValue(initialValue), endValue(initialValue) {
|
||||
}
|
||||
|
||||
float GetValue(const int time) const {
|
||||
switch (type) {
|
||||
case FADE_SINE: return GetSine(time);
|
||||
case FADE_INVERSE_SINE: return GetInverseSine(time);
|
||||
default: return GetLinear(time);
|
||||
}
|
||||
}
|
||||
|
||||
float GetLinear(const int time) const {
|
||||
return Interpolate(time, LinearFraction(time));
|
||||
}
|
||||
|
||||
float GetSine(const int time) const {
|
||||
const float fraction = LinearFraction(time);
|
||||
const float shaped = std::sin(fraction * 1.5707963267948966f);
|
||||
return Interpolate(time, shaped);
|
||||
}
|
||||
|
||||
float GetInverseSine(const int time) const {
|
||||
const float fraction = LinearFraction(time);
|
||||
const float shaped = 1.0f + std::sin(
|
||||
fraction * 1.5707963267948966f + 4.71238898038469f
|
||||
);
|
||||
return Interpolate(time, shaped);
|
||||
}
|
||||
|
||||
void FadeTowards(const float newEndValue, const int time,
|
||||
const int newDuration) {
|
||||
startValue = GetValue(time);
|
||||
startTime = time;
|
||||
endValue = newEndValue;
|
||||
duration = newDuration < 0 ? 0 : newDuration;
|
||||
}
|
||||
|
||||
void SetType(const type_t fadeType) {
|
||||
type = fadeType;
|
||||
}
|
||||
|
||||
private:
|
||||
type_t type;
|
||||
int startTime;
|
||||
int duration;
|
||||
float startValue;
|
||||
float endValue;
|
||||
|
||||
float LinearFraction(const int time) const {
|
||||
if (time <= startTime) {
|
||||
return 0.0f;
|
||||
}
|
||||
if (duration <= 0) {
|
||||
return 1.0f;
|
||||
}
|
||||
if (time >= startTime + duration) {
|
||||
return 1.0f;
|
||||
}
|
||||
return static_cast<float>(time - startTime) / static_cast<float>(duration);
|
||||
}
|
||||
|
||||
float Interpolate(const int time, const float fraction) const {
|
||||
if (time < startTime) {
|
||||
return startValue;
|
||||
}
|
||||
if (duration <= 0 || time >= startTime + duration) {
|
||||
return endValue;
|
||||
}
|
||||
return startValue + fraction * (endValue - startValue);
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(idFader) == 20, "Recovered idFader ABI changed");
|
||||
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include "idlib/precompiled.h"
|
||||
|
||||
class idMat3x4 {
|
||||
public:
|
||||
idMat3x4() {
|
||||
Identity();
|
||||
}
|
||||
|
||||
idMat3x4(const idMat3& rotation, const idVec3& translation) {
|
||||
for (int row = 0; row < 3; ++row) {
|
||||
for (int column = 0; column < 3; ++column) {
|
||||
mat[row * 4 + column] = rotation[column][row];
|
||||
}
|
||||
mat[row * 4 + 3] = translation[row];
|
||||
}
|
||||
}
|
||||
|
||||
void Identity() {
|
||||
for (int index = 0; index < 12; ++index) {
|
||||
mat[index] = 0.0f;
|
||||
}
|
||||
mat[0] = mat[5] = mat[10] = 1.0f;
|
||||
}
|
||||
|
||||
void Transform(idVec3& result, const idVec3& value) const {
|
||||
result.x = value.x * mat[0] + value.y * mat[1]
|
||||
+ value.z * mat[2] + mat[3];
|
||||
result.y = value.x * mat[4] + value.y * mat[5]
|
||||
+ value.z * mat[6] + mat[7];
|
||||
result.z = value.x * mat[8] + value.y * mat[9]
|
||||
+ value.z * mat[10] + mat[11];
|
||||
}
|
||||
|
||||
void Rotate(idMat3& result, const idMat3& value) const {
|
||||
for (int column = 0; column < 3; ++column) {
|
||||
result[column].x = mat[0] * value[column].x
|
||||
+ mat[1] * value[column].y + mat[2] * value[column].z;
|
||||
result[column].y = mat[4] * value[column].x
|
||||
+ mat[5] * value[column].y + mat[6] * value[column].z;
|
||||
result[column].z = mat[8] * value[column].x
|
||||
+ mat[9] * value[column].y + mat[10] * value[column].z;
|
||||
}
|
||||
}
|
||||
|
||||
void Invert() {
|
||||
const float old[12] = {
|
||||
mat[0], mat[1], mat[2], mat[3],
|
||||
mat[4], mat[5], mat[6], mat[7],
|
||||
mat[8], mat[9], mat[10], mat[11]
|
||||
};
|
||||
mat[0] = old[0]; mat[1] = old[4]; mat[2] = old[8];
|
||||
mat[4] = old[1]; mat[5] = old[5]; mat[6] = old[9];
|
||||
mat[8] = old[2]; mat[9] = old[6]; mat[10] = old[10];
|
||||
mat[3] = -(mat[0] * old[3] + mat[1] * old[7] + mat[2] * old[11]);
|
||||
mat[7] = -(mat[4] * old[3] + mat[5] * old[7] + mat[6] * old[11]);
|
||||
mat[11] = -(mat[8] * old[3] + mat[9] * old[7] + mat[10] * old[11]);
|
||||
}
|
||||
|
||||
void LeftTransposeMultiply(const idMat3& value) {
|
||||
float old[12];
|
||||
for (int index = 0; index < 12; ++index) {
|
||||
old[index] = mat[index];
|
||||
}
|
||||
for (int row = 0; row < 3; ++row) {
|
||||
for (int column = 0; column < 4; ++column) {
|
||||
mat[row * 4 + column] = value[0][row] * old[column]
|
||||
+ value[1][row] * old[4 + column]
|
||||
+ value[2][row] * old[8 + column];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float* ToFloatPtr() { return mat; }
|
||||
const float* ToFloatPtr() const { return mat; }
|
||||
|
||||
private:
|
||||
float mat[12];
|
||||
};
|
||||
|
||||
static_assert(sizeof(idMat3x4) == 48, "Recovered idMat3x4 ABI changed");
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "mathlib.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
int InterleaveBits(const int x, const int y) {
|
||||
const std::uint32_t xBits = static_cast<std::uint32_t>(x);
|
||||
const std::uint32_t yBits = static_cast<std::uint32_t>(y);
|
||||
std::uint32_t interleaved = 0;
|
||||
|
||||
for (int bit = 0; bit < 16; ++bit) {
|
||||
interleaved |= ((xBits >> bit) & 1u) << (bit * 2);
|
||||
interleaved |= ((yBits >> bit) & 1u) << (bit * 2 + 1);
|
||||
}
|
||||
|
||||
return static_cast<int>(interleaved);
|
||||
}
|
||||
|
||||
void DeInterleaveBits(const int bits, int& x, int& y) {
|
||||
const std::uint32_t interleaved = static_cast<std::uint32_t>(bits);
|
||||
std::uint32_t xBits = 0;
|
||||
std::uint32_t yBits = 0;
|
||||
|
||||
for (int bit = 0; bit < 16; ++bit) {
|
||||
xBits |= ((interleaved >> (bit * 2)) & 1u) << bit;
|
||||
yBits |= ((interleaved >> (bit * 2 + 1)) & 1u) << bit;
|
||||
}
|
||||
|
||||
x = static_cast<int>(xBits);
|
||||
y = static_cast<int>(yBits);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
// Recovered PDB signatures:
|
||||
// ?InterleaveBits@@YAHHH@Z
|
||||
// ?DeInterleaveBits@@YAXHAAH0@Z
|
||||
int InterleaveBits(int x, int y);
|
||||
void DeInterleaveBits(int bits, int& x, int& y);
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
#include "spatialmat.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <malloc.h>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t SPATIAL_MAT_FLOATS =
|
||||
idSpatialMat::MAX_ROWS * idSpatialMat::ROW_STRIDE;
|
||||
constexpr std::size_t SPATIAL_MAT_BYTES =
|
||||
SPATIAL_MAT_FLOATS * sizeof(float);
|
||||
|
||||
float* AllocSpatialMat() {
|
||||
return static_cast<float*>(_aligned_malloc(SPATIAL_MAT_BYTES, 16));
|
||||
}
|
||||
|
||||
bool IsValidSize(const int rows, const int columns) {
|
||||
return rows >= 0 && rows <= idSpatialMat::MAX_ROWS
|
||||
&& columns >= 0 && columns <= idSpatialMat::MAX_COLUMNS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
idSpatialMat::idSpatialMat()
|
||||
: numRows(0), numColumns(0), allocatedRows(0), mat(nullptr) {
|
||||
}
|
||||
|
||||
idSpatialMat::idSpatialMat(const int rows, const int columns)
|
||||
: idSpatialMat() {
|
||||
SetSize(rows, columns);
|
||||
}
|
||||
|
||||
idSpatialMat::idSpatialMat(const idSpatialMat& other)
|
||||
: idSpatialMat() {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
idSpatialMat::~idSpatialMat() {
|
||||
if (mat != nullptr && allocatedRows > 0) {
|
||||
_aligned_free(mat);
|
||||
}
|
||||
}
|
||||
|
||||
idSpatialMat& idSpatialMat::operator=(const idSpatialMat& other) {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
SetSize(other.numRows, other.numColumns);
|
||||
if (mat != nullptr && other.mat != nullptr) {
|
||||
std::memcpy(mat, other.mat, SPATIAL_MAT_BYTES);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void idSpatialMat::SetSize(const int rows, const int columns) {
|
||||
if (!IsValidSize(rows, columns)) {
|
||||
numRows = 0;
|
||||
numColumns = 0;
|
||||
return;
|
||||
}
|
||||
if (mat == nullptr) {
|
||||
mat = AllocSpatialMat();
|
||||
if (mat == nullptr) {
|
||||
numRows = 0;
|
||||
numColumns = 0;
|
||||
allocatedRows = 0;
|
||||
return;
|
||||
}
|
||||
allocatedRows = MAX_ROWS;
|
||||
std::memset(mat, 0, SPATIAL_MAT_BYTES);
|
||||
}
|
||||
numRows = rows;
|
||||
numColumns = columns;
|
||||
ClearPadding();
|
||||
}
|
||||
|
||||
void idSpatialMat::ChangeNumRows(const int rows) {
|
||||
if (rows < 0 || rows > MAX_ROWS || mat == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (rows != numRows) {
|
||||
const int firstClearedRow = std::min(rows, numRows);
|
||||
const int rowCount = std::max(rows, numRows) - firstClearedRow;
|
||||
if (rowCount > 0) {
|
||||
std::memset(mat + firstClearedRow * ROW_STRIDE, 0,
|
||||
static_cast<std::size_t>(rowCount * ROW_STRIDE) * sizeof(float));
|
||||
}
|
||||
numRows = rows;
|
||||
}
|
||||
}
|
||||
|
||||
void idSpatialMat::Zero(const int rows, const int columns) {
|
||||
SetSize(rows, columns);
|
||||
Zero();
|
||||
}
|
||||
|
||||
void idSpatialMat::Zero() {
|
||||
if (mat != nullptr) {
|
||||
std::memset(mat, 0, SPATIAL_MAT_BYTES);
|
||||
}
|
||||
}
|
||||
|
||||
void idSpatialMat::Set(const idMat3& m1, const idMat3& m2) {
|
||||
SetSize(3, 6);
|
||||
if (mat == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (int row = 0; row < 3; ++row) {
|
||||
for (int column = 0; column < 3; ++column) {
|
||||
(*this)(row, column) = m1[row][column];
|
||||
(*this)(row, column + 3) = m2[row][column];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void idSpatialMat::Set(const idMat3& m1, const idMat3& m2,
|
||||
const idMat3& m3, const idMat3& m4) {
|
||||
SetSize(6, 6);
|
||||
if (mat == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (int row = 0; row < 3; ++row) {
|
||||
for (int column = 0; column < 3; ++column) {
|
||||
(*this)(row, column) = m1[row][column];
|
||||
(*this)(row, column + 3) = m2[row][column];
|
||||
(*this)(row + 3, column) = m3[row][column];
|
||||
(*this)(row + 3, column + 3) = m4[row][column];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void idSpatialMat::SetData(const int rows, const int columns, float* data) {
|
||||
if (!IsValidSize(rows, columns) || data == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (mat != nullptr && allocatedRows > 0) {
|
||||
_aligned_free(mat);
|
||||
}
|
||||
numRows = rows;
|
||||
numColumns = columns;
|
||||
allocatedRows = -MAX_ROWS;
|
||||
mat = data;
|
||||
ClearPadding();
|
||||
}
|
||||
|
||||
void idSpatialMat::ClearPadding() {
|
||||
if (mat == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (int row = 0; row < MAX_ROWS; ++row) {
|
||||
const int first = row < numRows ? numColumns : 0;
|
||||
std::fill(mat + row * ROW_STRIDE + first,
|
||||
mat + (row + 1) * ROW_STRIDE, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void idSpatialMat::Negate() {
|
||||
if (mat == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (int row = 0; row < numRows; ++row) {
|
||||
for (int column = 0; column < ROW_STRIDE; ++column) {
|
||||
(*this)[row][column] = -(*this)[row][column];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void idSpatialMat::Transpose(idSpatialMat& dst) const {
|
||||
float values[MAX_ROWS][MAX_COLUMNS] = {};
|
||||
for (int row = 0; row < numRows; ++row) {
|
||||
for (int column = 0; column < numColumns; ++column) {
|
||||
values[column][row] = (*this)(row, column);
|
||||
}
|
||||
}
|
||||
dst.Zero(numColumns, numRows);
|
||||
for (int row = 0; row < numColumns; ++row) {
|
||||
for (int column = 0; column < numRows; ++column) {
|
||||
dst(row, column) = values[row][column];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void idSpatialMat::Subtract(const idSpatialMat& other) {
|
||||
if (mat == nullptr || other.mat == nullptr
|
||||
|| numRows != other.numRows || numColumns != other.numColumns) {
|
||||
return;
|
||||
}
|
||||
for (int index = 0; index < MAX_ROWS * ROW_STRIDE; ++index) {
|
||||
mat[index] -= other.mat[index];
|
||||
}
|
||||
}
|
||||
|
||||
void idSpatialMat::Multiply(idSpatialVec& dst, const idSpatialVec& vec) const {
|
||||
float result[MAX_ROWS] = {};
|
||||
const int terms = std::min(numColumns, vec.GetSize());
|
||||
for (int row = 0; row < numRows; ++row) {
|
||||
for (int column = 0; column < terms; ++column) {
|
||||
result[row] += (*this)(row, column) * vec[column];
|
||||
}
|
||||
}
|
||||
dst.SetSize(numRows);
|
||||
for (int row = 0; row < numRows; ++row) {
|
||||
dst[row] = result[row];
|
||||
}
|
||||
}
|
||||
|
||||
void idSpatialMat::MultiplyAdd(idSpatialVec& dst, const idSpatialVec& vec) const {
|
||||
if (dst.GetSize() < numRows) {
|
||||
dst.SetSize(numRows);
|
||||
}
|
||||
const int terms = std::min(numColumns, vec.GetSize());
|
||||
for (int row = 0; row < numRows; ++row) {
|
||||
float value = 0.0f;
|
||||
for (int column = 0; column < terms; ++column) {
|
||||
value += (*this)(row, column) * vec[column];
|
||||
}
|
||||
dst[row] += value;
|
||||
}
|
||||
}
|
||||
|
||||
void idSpatialMat::MultiplySub(idSpatialVec& dst, const idSpatialVec& vec) const {
|
||||
if (dst.GetSize() < numRows) {
|
||||
dst.SetSize(numRows);
|
||||
}
|
||||
const int terms = std::min(numColumns, vec.GetSize());
|
||||
for (int row = 0; row < numRows; ++row) {
|
||||
float value = 0.0f;
|
||||
for (int column = 0; column < terms; ++column) {
|
||||
value += (*this)(row, column) * vec[column];
|
||||
}
|
||||
dst[row] -= value;
|
||||
}
|
||||
}
|
||||
|
||||
void idSpatialMat::TransposeMultiplyAdd(idSpatialVec& dst,
|
||||
const idSpatialVec& vec) const {
|
||||
if (dst.GetSize() < numColumns) {
|
||||
dst.SetSize(numColumns);
|
||||
}
|
||||
const int terms = std::min(numRows, vec.GetSize());
|
||||
for (int column = 0; column < numColumns; ++column) {
|
||||
float value = 0.0f;
|
||||
for (int row = 0; row < terms; ++row) {
|
||||
value += (*this)(row, column) * vec[row];
|
||||
}
|
||||
dst[column] += value;
|
||||
}
|
||||
}
|
||||
|
||||
void idSpatialMat::TransposeMultiplySub(idSpatialVec& dst,
|
||||
const idSpatialVec& vec) const {
|
||||
if (dst.GetSize() < numColumns) {
|
||||
dst.SetSize(numColumns);
|
||||
}
|
||||
const int terms = std::min(numRows, vec.GetSize());
|
||||
for (int column = 0; column < numColumns; ++column) {
|
||||
float value = 0.0f;
|
||||
for (int row = 0; row < terms; ++row) {
|
||||
value += (*this)(row, column) * vec[row];
|
||||
}
|
||||
dst[column] -= value;
|
||||
}
|
||||
}
|
||||
|
||||
void idSpatialMat::Multiply(idSpatialMat& dst,
|
||||
const idSpatialMat& other) const {
|
||||
if (numColumns != other.numRows) {
|
||||
dst.Zero(0, 0);
|
||||
return;
|
||||
}
|
||||
float values[MAX_ROWS][MAX_COLUMNS] = {};
|
||||
for (int row = 0; row < numRows; ++row) {
|
||||
for (int column = 0; column < other.numColumns; ++column) {
|
||||
for (int term = 0; term < numColumns; ++term) {
|
||||
values[row][column] +=
|
||||
(*this)(row, term) * other(term, column);
|
||||
}
|
||||
}
|
||||
}
|
||||
dst.Zero(numRows, other.numColumns);
|
||||
for (int row = 0; row < numRows; ++row) {
|
||||
for (int column = 0; column < other.numColumns; ++column) {
|
||||
dst(row, column) = values[row][column];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void idSpatialMat::TransposeMultiply(idSpatialMat& dst,
|
||||
const idSpatialMat& other) const {
|
||||
if (numRows != other.numRows) {
|
||||
dst.Zero(0, 0);
|
||||
return;
|
||||
}
|
||||
float values[MAX_ROWS][MAX_COLUMNS] = {};
|
||||
for (int row = 0; row < numColumns; ++row) {
|
||||
for (int column = 0; column < other.numColumns; ++column) {
|
||||
for (int term = 0; term < numRows; ++term) {
|
||||
values[row][column] +=
|
||||
(*this)(term, row) * other(term, column);
|
||||
}
|
||||
}
|
||||
}
|
||||
dst.Zero(numColumns, other.numColumns);
|
||||
for (int row = 0; row < numColumns; ++row) {
|
||||
for (int column = 0; column < other.numColumns; ++column) {
|
||||
dst(row, column) = values[row][column];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool idSpatialMat::Inverse(idSpatialMat& dst) const {
|
||||
if (numRows != numColumns || numRows < 1 || numRows > MAX_ROWS) {
|
||||
return false;
|
||||
}
|
||||
switch (numRows) {
|
||||
case 1: return Inverse1x1(dst);
|
||||
case 2: return Inverse2x2(dst);
|
||||
case 3: return Inverse3x3(dst);
|
||||
case 4: return Inverse4x4(dst);
|
||||
case 5: return Inverse5x5(dst);
|
||||
case 6: return Inverse6x6(dst);
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool idSpatialMat::InverseNxN(idSpatialMat& dst, const int dimension) const {
|
||||
double work[MAX_ROWS][MAX_ROWS * 2] = {};
|
||||
for (int row = 0; row < dimension; ++row) {
|
||||
for (int column = 0; column < dimension; ++column) {
|
||||
work[row][column] = (*this)(row, column);
|
||||
}
|
||||
work[row][dimension + row] = 1.0;
|
||||
}
|
||||
|
||||
for (int pivotColumn = 0; pivotColumn < dimension; ++pivotColumn) {
|
||||
int pivotRow = pivotColumn;
|
||||
for (int row = pivotColumn + 1; row < dimension; ++row) {
|
||||
if (std::fabs(work[row][pivotColumn])
|
||||
> std::fabs(work[pivotRow][pivotColumn])) {
|
||||
pivotRow = row;
|
||||
}
|
||||
}
|
||||
if (std::fabs(work[pivotRow][pivotColumn]) < 1.0e-14) {
|
||||
return false;
|
||||
}
|
||||
if (pivotRow != pivotColumn) {
|
||||
for (int column = 0; column < dimension * 2; ++column) {
|
||||
std::swap(work[pivotRow][column], work[pivotColumn][column]);
|
||||
}
|
||||
}
|
||||
const double reciprocal = 1.0 / work[pivotColumn][pivotColumn];
|
||||
for (int column = 0; column < dimension * 2; ++column) {
|
||||
work[pivotColumn][column] *= reciprocal;
|
||||
}
|
||||
for (int row = 0; row < dimension; ++row) {
|
||||
if (row == pivotColumn) {
|
||||
continue;
|
||||
}
|
||||
const double scale = work[row][pivotColumn];
|
||||
for (int column = 0; column < dimension * 2; ++column) {
|
||||
work[row][column] -= scale * work[pivotColumn][column];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dst.Zero(dimension, dimension);
|
||||
for (int row = 0; row < dimension; ++row) {
|
||||
for (int column = 0; column < dimension; ++column) {
|
||||
dst(row, column) = static_cast<float>(work[row][dimension + column]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool idSpatialMat::Inverse1x1(idSpatialMat& dst) const { return InverseNxN(dst, 1); }
|
||||
bool idSpatialMat::Inverse2x2(idSpatialMat& dst) const { return InverseNxN(dst, 2); }
|
||||
bool idSpatialMat::Inverse3x3(idSpatialMat& dst) const { return InverseNxN(dst, 3); }
|
||||
bool idSpatialMat::Inverse4x4(idSpatialMat& dst) const { return InverseNxN(dst, 4); }
|
||||
bool idSpatialMat::Inverse5x5(idSpatialMat& dst) const { return InverseNxN(dst, 5); }
|
||||
bool idSpatialMat::Inverse6x6(idSpatialMat& dst) const { return InverseNxN(dst, 6); }
|
||||
|
||||
idSpatialVec idSpatialMat::SubSpatialVec(const int row) const {
|
||||
idSpatialVec result;
|
||||
if (mat != nullptr && row >= 0 && row < numRows) {
|
||||
result.SetData(MAX_COLUMNS, mat + row * ROW_STRIDE);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "idlib/precompiled.h"
|
||||
|
||||
#include "spatialvec.h"
|
||||
|
||||
// Tungsten stores every spatial matrix in a six-row, eight-float-stride slab.
|
||||
// The two padding floats per row are intentional: the Xenon implementation
|
||||
// loads complete VMX vectors from each half-row.
|
||||
class idSpatialMat {
|
||||
public:
|
||||
static const int MAX_ROWS = 6;
|
||||
static const int MAX_COLUMNS = 6;
|
||||
static const int ROW_STRIDE = 8;
|
||||
|
||||
idSpatialMat();
|
||||
idSpatialMat(int rows, int columns);
|
||||
idSpatialMat(const idSpatialMat& other);
|
||||
~idSpatialMat();
|
||||
|
||||
idSpatialMat& operator=(const idSpatialMat& other);
|
||||
|
||||
void SetSize(int rows, int columns);
|
||||
void ChangeNumRows(int rows);
|
||||
void Zero(int rows, int columns);
|
||||
void Zero();
|
||||
void Set(const idMat3& m1, const idMat3& m2);
|
||||
void Set(const idMat3& m1, const idMat3& m2,
|
||||
const idMat3& m3, const idMat3& m4);
|
||||
void SetData(int rows, int columns, float* data);
|
||||
void Negate();
|
||||
|
||||
void Transpose(idSpatialMat& dst) const;
|
||||
void Subtract(const idSpatialMat& other);
|
||||
|
||||
void Multiply(idSpatialVec& dst, const idSpatialVec& vec) const;
|
||||
void MultiplyAdd(idSpatialVec& dst, const idSpatialVec& vec) const;
|
||||
void MultiplySub(idSpatialVec& dst, const idSpatialVec& vec) const;
|
||||
void TransposeMultiplyAdd(idSpatialVec& dst, const idSpatialVec& vec) const;
|
||||
void TransposeMultiplySub(idSpatialVec& dst, const idSpatialVec& vec) const;
|
||||
void Multiply(idSpatialMat& dst, const idSpatialMat& other) const;
|
||||
void TransposeMultiply(idSpatialMat& dst, const idSpatialMat& other) const;
|
||||
|
||||
bool Inverse(idSpatialMat& dst) const;
|
||||
idSpatialVec SubSpatialVec(int row) const;
|
||||
|
||||
int GetNumRows() const { return numRows; }
|
||||
int GetNumColumns() const { return numColumns; }
|
||||
int GetAllocatedRows() const { return allocatedRows; }
|
||||
float* ToFloatPtr() { return mat; }
|
||||
const float* ToFloatPtr() const { return mat; }
|
||||
|
||||
float* operator[](int row) { return mat + row * ROW_STRIDE; }
|
||||
const float* operator[](int row) const { return mat + row * ROW_STRIDE; }
|
||||
float& operator()(int row, int column) { return mat[row * ROW_STRIDE + column]; }
|
||||
float operator()(int row, int column) const { return mat[row * ROW_STRIDE + column]; }
|
||||
|
||||
private:
|
||||
bool InverseNxN(idSpatialMat& dst, int dimension) const;
|
||||
bool Inverse1x1(idSpatialMat& dst) const;
|
||||
bool Inverse2x2(idSpatialMat& dst) const;
|
||||
bool Inverse3x3(idSpatialMat& dst) const;
|
||||
bool Inverse4x4(idSpatialMat& dst) const;
|
||||
bool Inverse5x5(idSpatialMat& dst) const;
|
||||
bool Inverse6x6(idSpatialMat& dst) const;
|
||||
void ClearPadding();
|
||||
|
||||
int numRows;
|
||||
int numColumns;
|
||||
int allocatedRows;
|
||||
float* mat;
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idSpatialMat) == 16, "Recovered idSpatialMat ABI changed");
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
class idSpatialVec {
|
||||
public:
|
||||
idSpatialVec()
|
||||
: size(0), allocated(0), p(nullptr) {
|
||||
}
|
||||
|
||||
explicit idSpatialVec(const int length)
|
||||
: idSpatialVec() {
|
||||
SetSize(length);
|
||||
}
|
||||
|
||||
idSpatialVec(const idSpatialVec& other)
|
||||
: idSpatialVec() {
|
||||
SetSize(other.size);
|
||||
if (p != nullptr && other.p != nullptr) {
|
||||
std::memcpy(p, other.p, sizeof(float) * other.size);
|
||||
}
|
||||
}
|
||||
|
||||
idSpatialVec(idSpatialVec&& other) noexcept
|
||||
: size(other.size), allocated(other.allocated), p(other.p) {
|
||||
other.size = 0;
|
||||
other.allocated = 0;
|
||||
other.p = nullptr;
|
||||
}
|
||||
|
||||
~idSpatialVec() {
|
||||
if (allocated > 0) {
|
||||
std::free(p);
|
||||
}
|
||||
}
|
||||
|
||||
idSpatialVec& operator=(const idSpatialVec& other) {
|
||||
if (this != &other) {
|
||||
SetSize(other.size);
|
||||
if (p != nullptr && other.p != nullptr) {
|
||||
std::memcpy(p, other.p, sizeof(float) * other.size);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
idSpatialVec& operator=(idSpatialVec&& other) noexcept {
|
||||
if (this != &other) {
|
||||
if (allocated > 0) {
|
||||
std::free(p);
|
||||
}
|
||||
size = other.size;
|
||||
allocated = other.allocated;
|
||||
p = other.p;
|
||||
other.size = 0;
|
||||
other.allocated = 0;
|
||||
other.p = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void SetData(const int length, float* data) {
|
||||
if (allocated > 0) {
|
||||
std::free(p);
|
||||
}
|
||||
p = data;
|
||||
size = static_cast<std::int16_t>(std::max(0, length));
|
||||
allocated = static_cast<std::int16_t>(-std::max(8, length));
|
||||
if (p != nullptr) {
|
||||
for (int index = size; index < -allocated; ++index) {
|
||||
p[index] = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SetSize(const int newSize) {
|
||||
if (newSize < 0 || newSize > 32767) {
|
||||
return false;
|
||||
}
|
||||
const int capacity = allocated < 0 ? -allocated : allocated;
|
||||
if (p == nullptr || newSize > capacity) {
|
||||
const int newCapacity = std::max(8, (newSize + 7) & ~7);
|
||||
float* const replacement = static_cast<float*>(
|
||||
std::calloc(static_cast<std::size_t>(newCapacity), sizeof(float))
|
||||
);
|
||||
if (replacement == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (p != nullptr) {
|
||||
std::memcpy(replacement, p,
|
||||
sizeof(float) * static_cast<std::size_t>(std::min<int>(size, newSize)));
|
||||
}
|
||||
if (allocated > 0) {
|
||||
std::free(p);
|
||||
}
|
||||
p = replacement;
|
||||
allocated = static_cast<std::int16_t>(newCapacity);
|
||||
} else if (newSize > size) {
|
||||
std::memset(p + size, 0,
|
||||
sizeof(float) * static_cast<std::size_t>(newSize - size));
|
||||
}
|
||||
size = static_cast<std::int16_t>(newSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ChangeSize(const int newSize) {
|
||||
SetSize(newSize);
|
||||
}
|
||||
|
||||
void Zero() {
|
||||
if (p != nullptr) {
|
||||
std::memset(p, 0, sizeof(float) * static_cast<std::size_t>(size));
|
||||
}
|
||||
}
|
||||
|
||||
void Clamp(const float minimum, const float maximum) {
|
||||
for (int index = 0; index < size; ++index) {
|
||||
p[index] = std::max(minimum, std::min(maximum, p[index]));
|
||||
}
|
||||
}
|
||||
|
||||
float LengthSqr() const {
|
||||
float sum = 0.0f;
|
||||
for (int index = 0; index < size; ++index) {
|
||||
sum += p[index] * p[index];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
float Length() const {
|
||||
return std::sqrt(LengthSqr());
|
||||
}
|
||||
|
||||
int GetSize() const { return size; }
|
||||
float* ToFloatPtr() { return p; }
|
||||
const float* ToFloatPtr() const { return p; }
|
||||
|
||||
float& operator[](const int index) { return p[index]; }
|
||||
float operator[](const int index) const { return p[index]; }
|
||||
|
||||
private:
|
||||
std::int16_t size;
|
||||
std::int16_t allocated;
|
||||
float* p;
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idSpatialVec) == 8, "Recovered idSpatialVec ABI changed");
|
||||
#endif
|
||||
@@ -0,0 +1,117 @@
|
||||
#pragma once
|
||||
|
||||
#include "vector.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
template<typename vectorType>
|
||||
struct idSpringDimension;
|
||||
|
||||
template<> struct idSpringDimension<idVec1> { static constexpr int value = 1; };
|
||||
template<> struct idSpringDimension<idVec2> { static constexpr int value = 2; };
|
||||
template<> struct idSpringDimension<idVec3> { static constexpr int value = 3; };
|
||||
|
||||
template<typename vectorType>
|
||||
class idSpring {
|
||||
public:
|
||||
idSpring()
|
||||
: maxSpeed(0.0f), hasPMax(false), hasPMin(false),
|
||||
k(1.0f), c(2.0f), m(1.0f), restLength(0.0f) {
|
||||
p0.Zero();
|
||||
p1.Zero();
|
||||
vel.Zero();
|
||||
pMin.Zero();
|
||||
pMax.Zero();
|
||||
}
|
||||
|
||||
void SetConstants(float springConstant, const float dampingConstant) {
|
||||
k = std::min(10000.0f, std::max(0.0f, springConstant));
|
||||
c = dampingConstant < 0.0f ? 2.0f * std::sqrt(m * k)
|
||||
: dampingConstant;
|
||||
}
|
||||
|
||||
void SetMass(const float mass) { m = mass > 0.0f ? mass : 1.0f; }
|
||||
void SetRestLength(const float length) { restLength = std::max(0.0f, length); }
|
||||
void SetMaxSpeed(const float speed) { maxSpeed = speed; }
|
||||
void SetAnchor(const vectorType& anchor) { p0 = anchor; }
|
||||
void SetPosition(const vectorType& position) { p1 = position; }
|
||||
void SetVelocity(const vectorType& velocity) { vel = velocity; }
|
||||
void SetMinimum(const vectorType& minimum) { pMin = minimum; hasPMin = true; }
|
||||
void SetMaximum(const vectorType& maximum) { pMax = maximum; hasPMax = true; }
|
||||
void ClearMinimum() { hasPMin = false; }
|
||||
void ClearMaximum() { hasPMax = false; }
|
||||
|
||||
const vectorType& GetPosition() const { return p1; }
|
||||
const vectorType& GetVelocity() const { return vel; }
|
||||
|
||||
void Update(float deltaTime) {
|
||||
while (deltaTime > 0.0f) {
|
||||
const float step = std::min(deltaTime, 0.0085f);
|
||||
deltaTime -= step;
|
||||
|
||||
float distanceSquared = 0.0f;
|
||||
float difference[idSpringDimension<vectorType>::value];
|
||||
for (int index = 0; index < idSpringDimension<vectorType>::value; ++index) {
|
||||
difference[index] = p1[index] - p0[index];
|
||||
distanceSquared += difference[index] * difference[index];
|
||||
}
|
||||
const float distance = std::sqrt(distanceSquared);
|
||||
const float inverseDistance = distance > 0.00001f ? 1.0f / distance : 0.0f;
|
||||
const float springForce = -(distance - restLength) * k;
|
||||
for (int index = 0; index < idSpringDimension<vectorType>::value; ++index) {
|
||||
const float force = difference[index] * inverseDistance * springForce
|
||||
- vel[index] * c;
|
||||
vel[index] += (force / m) * step;
|
||||
}
|
||||
|
||||
float speedSquared = 0.0f;
|
||||
for (int index = 0; index < idSpringDimension<vectorType>::value; ++index) {
|
||||
speedSquared += vel[index] * vel[index];
|
||||
}
|
||||
const float speed = std::sqrt(speedSquared);
|
||||
if (maxSpeed > 0.0f && speed > maxSpeed) {
|
||||
const float scale = maxSpeed / speed;
|
||||
for (int index = 0; index < idSpringDimension<vectorType>::value; ++index) {
|
||||
vel[index] *= scale;
|
||||
}
|
||||
}
|
||||
if (speed < 0.00001f) {
|
||||
vel.Zero();
|
||||
}
|
||||
for (int index = 0; index < idSpringDimension<vectorType>::value; ++index) {
|
||||
p1[index] += vel[index] * step;
|
||||
}
|
||||
if (distance < 0.00001f) {
|
||||
p1 = p0;
|
||||
}
|
||||
}
|
||||
|
||||
for (int index = 0; index < idSpringDimension<vectorType>::value; ++index) {
|
||||
if (hasPMin) {
|
||||
p1[index] = std::max(p1[index], pMin[index]);
|
||||
}
|
||||
if (hasPMax) {
|
||||
p1[index] = std::min(p1[index], pMax[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
vectorType p0;
|
||||
vectorType p1;
|
||||
vectorType vel;
|
||||
float maxSpeed;
|
||||
vectorType pMin;
|
||||
vectorType pMax;
|
||||
bool hasPMax;
|
||||
bool hasPMin;
|
||||
float k;
|
||||
float c;
|
||||
float m;
|
||||
float restLength;
|
||||
};
|
||||
|
||||
static_assert(sizeof(idSpring<idVec1>) == 44, "Recovered idSpring<idVec1> ABI changed");
|
||||
static_assert(sizeof(idSpring<idVec2>) == 64, "Recovered idSpring<idVec2> ABI changed");
|
||||
static_assert(sizeof(idSpring<idVec3>) == 84, "Recovered idSpring<idVec3> ABI changed");
|
||||
@@ -0,0 +1,329 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
class idVec1 {
|
||||
public:
|
||||
float x;
|
||||
|
||||
idVec1() = default;
|
||||
explicit idVec1(const float newX) : x(newX) {}
|
||||
void Zero() { x = 0.0f; }
|
||||
float operator[](const int) const { return x; }
|
||||
float& operator[](const int) { return x; }
|
||||
};
|
||||
|
||||
static_assert(sizeof(idVec1) == 4, "Recovered idVec1 layout changed");
|
||||
|
||||
// Minimal recovered ABI surface for tungsten's idVec2. More vector operations
|
||||
// will move here as their out-of-line idTech 5 implementations are activated.
|
||||
class idVec2 {
|
||||
public:
|
||||
float x;
|
||||
float y;
|
||||
|
||||
idVec2() = default;
|
||||
|
||||
idVec2(const float newX, const float newY)
|
||||
: x(newX)
|
||||
, y(newY) {
|
||||
}
|
||||
|
||||
void Set(const float newX, const float newY) {
|
||||
x = newX;
|
||||
y = newY;
|
||||
}
|
||||
|
||||
void Zero() {
|
||||
x = 0.0f;
|
||||
y = 0.0f;
|
||||
}
|
||||
|
||||
float operator[](const int index) const {
|
||||
assert(index >= 0 && index < 2);
|
||||
return (&x)[index];
|
||||
}
|
||||
|
||||
float& operator[](const int index) {
|
||||
assert(index >= 0 && index < 2);
|
||||
return (&x)[index];
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(idVec2) == 8, "Recovered idVec2 layout changed");
|
||||
|
||||
// Minimal recovered ABI surface for tungsten's idVec3. The class deliberately
|
||||
// stays a three-float POD layout; Xbox-only SIMD assumptions belong in the PC
|
||||
// portability layer rather than in this type.
|
||||
class idVec3 {
|
||||
public:
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
|
||||
idVec3() = default;
|
||||
|
||||
idVec3(const float newX, const float newY, const float newZ)
|
||||
: x(newX)
|
||||
, y(newY)
|
||||
, z(newZ) {
|
||||
}
|
||||
|
||||
void Set(const float newX, const float newY, const float newZ) {
|
||||
x = newX;
|
||||
y = newY;
|
||||
z = newZ;
|
||||
}
|
||||
|
||||
void Zero() {
|
||||
x = 0.0f;
|
||||
y = 0.0f;
|
||||
z = 0.0f;
|
||||
}
|
||||
|
||||
float operator[](const int index) const {
|
||||
assert(index >= 0 && index < 3);
|
||||
return (&x)[index];
|
||||
}
|
||||
|
||||
float& operator[](const int index) {
|
||||
assert(index >= 0 && index < 3);
|
||||
return (&x)[index];
|
||||
}
|
||||
|
||||
idVec3 operator-() const {
|
||||
return idVec3(-x, -y, -z);
|
||||
}
|
||||
|
||||
idVec3 operator+(const idVec3& other) const {
|
||||
return idVec3(x + other.x, y + other.y, z + other.z);
|
||||
}
|
||||
|
||||
idVec3 operator-(const idVec3& other) const {
|
||||
return idVec3(x - other.x, y - other.y, z - other.z);
|
||||
}
|
||||
|
||||
idVec3 operator*(const float scale) const {
|
||||
return idVec3(x * scale, y * scale, z * scale);
|
||||
}
|
||||
|
||||
float Dot(const idVec3& other) const {
|
||||
return x * other.x + y * other.y + z * other.z;
|
||||
}
|
||||
|
||||
idVec3 Cross(const idVec3& other) const {
|
||||
return idVec3(
|
||||
y * other.z - z * other.y,
|
||||
z * other.x - x * other.z,
|
||||
x * other.y - y * other.x
|
||||
);
|
||||
}
|
||||
|
||||
float LengthSqr() const {
|
||||
return Dot(*this);
|
||||
}
|
||||
|
||||
float Length() const {
|
||||
return std::sqrt(LengthSqr());
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(idVec3) == 12, "Recovered idVec3 layout changed");
|
||||
|
||||
class idMat3 {
|
||||
public:
|
||||
idVec3 mat[3];
|
||||
|
||||
idMat3() = default;
|
||||
explicit idMat3(float diagonal) {
|
||||
mat[0].Set(diagonal, 0.0f, 0.0f);
|
||||
mat[1].Set(0.0f, diagonal, 0.0f);
|
||||
mat[2].Set(0.0f, 0.0f, diagonal);
|
||||
}
|
||||
|
||||
idVec3& operator[](const int index) { return mat[index]; }
|
||||
const idVec3& operator[](const int index) const { return mat[index]; }
|
||||
};
|
||||
|
||||
static_assert(sizeof(idMat3) == 36, "Recovered idMat3 layout changed");
|
||||
|
||||
class idVec4 {
|
||||
public:
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float w;
|
||||
|
||||
idVec4() = default;
|
||||
|
||||
idVec4(
|
||||
const float newX,
|
||||
const float newY,
|
||||
const float newZ,
|
||||
const float newW
|
||||
)
|
||||
: x(newX)
|
||||
, y(newY)
|
||||
, z(newZ)
|
||||
, w(newW) {
|
||||
}
|
||||
|
||||
void Set(
|
||||
const float newX,
|
||||
const float newY,
|
||||
const float newZ,
|
||||
const float newW
|
||||
) {
|
||||
x = newX;
|
||||
y = newY;
|
||||
z = newZ;
|
||||
w = newW;
|
||||
}
|
||||
|
||||
float operator[](const int index) const {
|
||||
assert(index >= 0 && index < 4);
|
||||
return (&x)[index];
|
||||
}
|
||||
|
||||
float& operator[](const int index) {
|
||||
assert(index >= 0 && index < 4);
|
||||
return (&x)[index];
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(idVec4) == 16, "Recovered idVec4 layout changed");
|
||||
|
||||
class idAngles {
|
||||
public:
|
||||
float pitch;
|
||||
float yaw;
|
||||
float roll;
|
||||
|
||||
idAngles() = default;
|
||||
idAngles(const float newPitch, const float newYaw, const float newRoll)
|
||||
: pitch(newPitch), yaw(newYaw), roll(newRoll) {
|
||||
}
|
||||
|
||||
float operator[](const int index) const { return (&pitch)[index]; }
|
||||
float& operator[](const int index) { return (&pitch)[index]; }
|
||||
};
|
||||
|
||||
static_assert(sizeof(idAngles) == 12, "Recovered idAngles layout changed");
|
||||
|
||||
class idQuat {
|
||||
public:
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float w;
|
||||
|
||||
idQuat() = default;
|
||||
idQuat(const float newX, const float newY, const float newZ, const float newW)
|
||||
: x(newX), y(newY), z(newZ), w(newW) {
|
||||
}
|
||||
|
||||
float operator[](const int index) const { return (&x)[index]; }
|
||||
float& operator[](const int index) { return (&x)[index]; }
|
||||
};
|
||||
|
||||
static_assert(sizeof(idQuat) == 16, "Recovered idQuat layout changed");
|
||||
|
||||
// The Xbox 360 type-information stream serializes the dynamic math types by
|
||||
// their three/four-field facades. Keep these definitions allocation-simple on
|
||||
// the standalone recovery targets; the complete idLib target uses BFG's
|
||||
// layout-compatible implementations.
|
||||
class idVecX {
|
||||
public:
|
||||
idVecX() : size(0), alloced(0), p(nullptr) {}
|
||||
explicit idVecX(const int newSize) : idVecX() { SetSize(newSize); }
|
||||
idVecX(const idVecX& other) : idVecX() {
|
||||
SetSize(other.size);
|
||||
if (size > 0) std::memcpy(p, other.p, sizeof(float) * size);
|
||||
}
|
||||
~idVecX() { std::free(p); }
|
||||
|
||||
idVecX& operator=(const idVecX& other) {
|
||||
if (this != &other) {
|
||||
SetSize(other.size);
|
||||
if (size > 0) std::memcpy(p, other.p, sizeof(float) * size);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void SetSize(const int newSize) {
|
||||
const int safeSize = newSize > 0 ? newSize : 0;
|
||||
if (safeSize > alloced) {
|
||||
float* const replacement = static_cast<float*>(
|
||||
std::realloc(p, sizeof(float) * safeSize));
|
||||
if (replacement == nullptr) return;
|
||||
p = replacement;
|
||||
alloced = safeSize;
|
||||
}
|
||||
size = safeSize;
|
||||
}
|
||||
int GetSize() const { return size; }
|
||||
float& operator[](const int index) { return p[index]; }
|
||||
float operator[](const int index) const { return p[index]; }
|
||||
|
||||
private:
|
||||
int size;
|
||||
int alloced;
|
||||
float* p;
|
||||
};
|
||||
|
||||
static_assert(sizeof(idVecX) == 12, "Recovered idVecX layout changed");
|
||||
|
||||
class idMatX {
|
||||
public:
|
||||
idMatX() : numRows(0), numColumns(0), alloced(0), mat(nullptr) {}
|
||||
idMatX(const int rows, const int columns) : idMatX() {
|
||||
SetSize(rows, columns);
|
||||
}
|
||||
idMatX(const idMatX& other) : idMatX() {
|
||||
SetSize(other.numRows, other.numColumns);
|
||||
const int count = numRows * numColumns;
|
||||
if (count > 0) std::memcpy(mat, other.mat, sizeof(float) * count);
|
||||
}
|
||||
~idMatX() { std::free(mat); }
|
||||
|
||||
idMatX& operator=(const idMatX& other) {
|
||||
if (this != &other) {
|
||||
SetSize(other.numRows, other.numColumns);
|
||||
const int count = numRows * numColumns;
|
||||
if (count > 0) std::memcpy(mat, other.mat, sizeof(float) * count);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void SetSize(const int rows, const int columns) {
|
||||
const int safeRows = rows > 0 ? rows : 0;
|
||||
const int safeColumns = columns > 0 ? columns : 0;
|
||||
const int count = safeRows * safeColumns;
|
||||
if (count > alloced) {
|
||||
float* const replacement = static_cast<float*>(
|
||||
std::realloc(mat, sizeof(float) * count));
|
||||
if (replacement == nullptr) return;
|
||||
mat = replacement;
|
||||
alloced = count;
|
||||
}
|
||||
numRows = safeRows;
|
||||
numColumns = safeColumns;
|
||||
}
|
||||
int GetNumRows() const { return numRows; }
|
||||
int GetNumColumns() const { return numColumns; }
|
||||
float* operator[](const int row) { return mat + row * numColumns; }
|
||||
const float* operator[](const int row) const {
|
||||
return mat + row * numColumns;
|
||||
}
|
||||
|
||||
private:
|
||||
int numRows;
|
||||
int numColumns;
|
||||
int alloced;
|
||||
float* mat;
|
||||
};
|
||||
|
||||
static_assert(sizeof(idMatX) == 16, "Recovered idMatX layout changed");
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "metricrecord.h"
|
||||
|
||||
#include "metricsframework.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
FILE* MetricStream(idMetricFile* metricFile) {
|
||||
return metricFile == nullptr ? nullptr
|
||||
: static_cast<FILE*>(metricFile->fileHandle);
|
||||
}
|
||||
|
||||
void WriteU32(FILE* stream, const std::uint32_t value) {
|
||||
unsigned char bytes[4] = {
|
||||
static_cast<unsigned char>(value >> 0),
|
||||
static_cast<unsigned char>(value >> 8),
|
||||
static_cast<unsigned char>(value >> 16),
|
||||
static_cast<unsigned char>(value >> 24)
|
||||
};
|
||||
std::fwrite(bytes, 1, sizeof(bytes), stream);
|
||||
}
|
||||
|
||||
void WriteU64(FILE* stream, const std::uint64_t value) {
|
||||
WriteU32(stream, static_cast<std::uint32_t>(value));
|
||||
WriteU32(stream, static_cast<std::uint32_t>(value >> 32));
|
||||
}
|
||||
|
||||
void WriteString(FILE* stream, const char* text) {
|
||||
const char* const safeText = text == nullptr ? "" : text;
|
||||
const std::uint32_t length = static_cast<std::uint32_t>(std::strlen(safeText));
|
||||
WriteU32(stream, length);
|
||||
if (length > 0) std::fwrite(safeText, 1, length, stream);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
idMetricRecord::idMetricRecord(const idStr& recordName)
|
||||
: name(recordName), baseName(recordName) {
|
||||
}
|
||||
|
||||
void idMetricRecord::WriteHeader(idMetricFile* metricFile) {
|
||||
FILE* const stream = MetricStream(metricFile);
|
||||
if (stream == nullptr) return;
|
||||
WriteU32(stream, 1);
|
||||
WriteString(stream, "entryTime");
|
||||
WriteU32(stream, 0);
|
||||
}
|
||||
|
||||
void idMetricRecord::SerializeEntry(idMetricFile* metricFile) {
|
||||
FILE* const stream = MetricStream(metricFile);
|
||||
if (stream == nullptr) return;
|
||||
const std::uint64_t now = metricsFrameworkLocal.GetCurrentSystemTime();
|
||||
WriteU32(stream, static_cast<std::uint32_t>(now - metricFile->startTime));
|
||||
}
|
||||
|
||||
void idMetricRecord::AppendToName(const idStr& suffix) {
|
||||
name = baseName;
|
||||
name.Append(suffix);
|
||||
}
|
||||
|
||||
void idMetricFile::WriteFileInfo(const idStr& metricName) {
|
||||
FILE* const stream = MetricStream(this);
|
||||
if (stream == nullptr) return;
|
||||
startTime = metricsFrameworkLocal.GetCurrentSystemTime();
|
||||
WriteString(stream, "IDMETRIC");
|
||||
WriteU32(stream, 1);
|
||||
WriteString(stream, metricName.c_str());
|
||||
WriteU64(stream, startTime);
|
||||
}
|
||||
|
||||
void idMetricRecord::Serialize() {
|
||||
if (!metricsFrameworkLocal.IsRecording()) return;
|
||||
idMetricFile* const metricFile = metricsFrameworkLocal.GetFileHandle(name);
|
||||
if (metricFile == nullptr || metricFile->fileHandle == nullptr) return;
|
||||
if (!metricFile->headerWritten) {
|
||||
metricFile->headerWritten = true;
|
||||
metricFile->WriteFileInfo(name);
|
||||
WriteHeader(metricFile);
|
||||
}
|
||||
SerializeEntry(metricFile);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "idlib/text/str.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct idMetricFile {
|
||||
idMetricFile() : fileHandle(nullptr), headerWritten(false), startTime(0) {}
|
||||
void WriteFileInfo(const idStr& metricName);
|
||||
|
||||
void* fileHandle;
|
||||
bool headerWritten;
|
||||
std::uint64_t startTime;
|
||||
};
|
||||
|
||||
class idMetricRecord {
|
||||
public:
|
||||
explicit idMetricRecord(const idStr& recordName);
|
||||
virtual ~idMetricRecord() = default;
|
||||
|
||||
virtual void WriteHeader(idMetricFile* metricFile);
|
||||
virtual void SerializeEntry(idMetricFile* metricFile);
|
||||
|
||||
void AppendToName(const idStr& suffix);
|
||||
void Serialize();
|
||||
const idStr& GetName() const { return name; }
|
||||
|
||||
protected:
|
||||
idStr name;
|
||||
idStr baseName;
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idMetricFile) == 16, "Recovered idMetricFile ABI changed");
|
||||
static_assert(sizeof(idMetricRecord) == 68,
|
||||
"Recovered idMetricRecord ABI changed");
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
#include "metrics.h"
|
||||
|
||||
#include "metricsframework.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
FILE* MetricStream(idMetricFile* metricFile) {
|
||||
return metricFile == nullptr ? nullptr
|
||||
: static_cast<FILE*>(metricFile->fileHandle);
|
||||
}
|
||||
|
||||
void WriteU32(FILE* stream, const std::uint32_t value) {
|
||||
std::fwrite(&value, 1, sizeof(value), stream);
|
||||
}
|
||||
|
||||
void WriteFloat(FILE* stream, const float value) {
|
||||
std::fwrite(&value, 1, sizeof(value), stream);
|
||||
}
|
||||
|
||||
void WriteString(FILE* stream, const char* text) {
|
||||
const std::uint32_t length = static_cast<std::uint32_t>(std::strlen(text));
|
||||
WriteU32(stream, length);
|
||||
std::fwrite(text, 1, length, stream);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
idGaugeMetric::idGaugeMetric(const idStr& shortName, const idStr&)
|
||||
: idMetricRecord(shortName), count(0), min(0.0f), max(0.0f), mean(0.0f),
|
||||
mean2(0.0f), m2(0.0f), handle(-1), lastPushFrame(0), gameState(-1) {
|
||||
}
|
||||
|
||||
bool idGaugeMetric::CheckPushState() {
|
||||
const int pushFrame = metricsFrameworkLocal.GetPushFrame();
|
||||
if (pushFrame <= lastPushFrame || count == 0) return false;
|
||||
Serialize();
|
||||
lastPushFrame = pushFrame;
|
||||
count = 0;
|
||||
min = max = mean = mean2 = m2 = 0.0f;
|
||||
return true;
|
||||
}
|
||||
|
||||
void idGaugeMetric::Log(const float value, const bool skipPush) {
|
||||
if (count == 0) min = max = value;
|
||||
else {
|
||||
if (value < min) min = value;
|
||||
if (value > max) max = value;
|
||||
}
|
||||
const float delta = value - mean;
|
||||
++count;
|
||||
mean += delta / static_cast<float>(count);
|
||||
m2 += delta * (value - mean);
|
||||
mean2 += value * value;
|
||||
if (!skipPush) CheckPushState();
|
||||
}
|
||||
|
||||
void idGaugeMetric::WriteHeader(idMetricFile* metricFile) {
|
||||
FILE* const stream = MetricStream(metricFile);
|
||||
if (stream == nullptr) return;
|
||||
idMetricRecord::WriteHeader(metricFile);
|
||||
WriteU32(stream, 5);
|
||||
const char* const names[] = { "count", "min", "max", "mean", "stdDev" };
|
||||
for (int index = 0; index < 5; ++index) {
|
||||
WriteString(stream, names[index]);
|
||||
WriteU32(stream, index == 0 ? 0u : 1u);
|
||||
}
|
||||
}
|
||||
|
||||
void idGaugeMetric::SerializeEntry(idMetricFile* metricFile) {
|
||||
FILE* const stream = MetricStream(metricFile);
|
||||
if (stream == nullptr) return;
|
||||
idMetricRecord::SerializeEntry(metricFile);
|
||||
WriteU32(stream, static_cast<std::uint32_t>(count));
|
||||
WriteFloat(stream, min);
|
||||
WriteFloat(stream, max);
|
||||
WriteFloat(stream, mean);
|
||||
WriteFloat(stream, count > 0 ? std::sqrt(mean2 / count) : 0.0f);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "metricrecord.h"
|
||||
|
||||
class idGaugeMetric : public idMetricRecord {
|
||||
public:
|
||||
idGaugeMetric(const idStr& shortName, const idStr& description = idStr());
|
||||
|
||||
bool CheckPushState();
|
||||
void Log(float value, bool skipPush = false);
|
||||
void WriteHeader(idMetricFile* metricFile) override;
|
||||
void SerializeEntry(idMetricFile* metricFile) override;
|
||||
|
||||
int GetCount() const { return count; }
|
||||
float GetMin() const { return min; }
|
||||
float GetMax() const { return max; }
|
||||
float GetMean() const { return mean; }
|
||||
|
||||
private:
|
||||
int count;
|
||||
float min;
|
||||
float max;
|
||||
float mean;
|
||||
float mean2;
|
||||
float m2;
|
||||
int handle;
|
||||
int lastPushFrame;
|
||||
int gameState;
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idGaugeMetric) == 104,
|
||||
"Recovered idGaugeMetric ABI changed");
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
#include "metricsframework.h"
|
||||
|
||||
#include "idlib/sys/sys_time.h"
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
struct metricSlot_t {
|
||||
idMetricsFramework* owner;
|
||||
char name[128];
|
||||
char path[MAX_PATH];
|
||||
idMetricFile file;
|
||||
};
|
||||
|
||||
struct frameworkConfig_t {
|
||||
idMetricsFramework* owner;
|
||||
char directory[MAX_PATH];
|
||||
int heartbeatMilliseconds;
|
||||
};
|
||||
|
||||
metricSlot_t metricSlots[64] = {};
|
||||
frameworkConfig_t frameworkConfigs[8] = {};
|
||||
|
||||
frameworkConfig_t& ConfigFor(idMetricsFramework* owner) {
|
||||
for (frameworkConfig_t& config : frameworkConfigs) {
|
||||
if (config.owner == owner) return config;
|
||||
}
|
||||
for (frameworkConfig_t& config : frameworkConfigs) {
|
||||
if (config.owner == nullptr) {
|
||||
config.owner = owner;
|
||||
std::strcpy(config.directory, "metrics");
|
||||
config.heartbeatMilliseconds = 250;
|
||||
return config;
|
||||
}
|
||||
}
|
||||
return frameworkConfigs[0];
|
||||
}
|
||||
|
||||
void WriteU32(FILE* stream, const std::uint32_t value) {
|
||||
std::fwrite(&value, 1, sizeof(value), stream);
|
||||
}
|
||||
|
||||
void WriteString(FILE* stream, const char* text) {
|
||||
const std::uint32_t length = static_cast<std::uint32_t>(std::strlen(text));
|
||||
WriteU32(stream, length);
|
||||
std::fwrite(text, 1, length, stream);
|
||||
}
|
||||
|
||||
void SanitizeName(const char* source, char* destination, const int capacity) {
|
||||
int out = 0;
|
||||
for (const char* cursor = source; *cursor != '\0' && out + 1 < capacity;
|
||||
++cursor) {
|
||||
const unsigned char value = static_cast<unsigned char>(*cursor);
|
||||
destination[out++] = (value >= 'a' && value <= 'z')
|
||||
|| (value >= 'A' && value <= 'Z')
|
||||
|| (value >= '0' && value <= '9') || value == '-' || value == '_'
|
||||
? static_cast<char>(value) : '_';
|
||||
}
|
||||
destination[out] = '\0';
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
idMetricsFramework metricsFrameworkLocal;
|
||||
|
||||
idMetricsFramework::MachineInfo::MachineInfo()
|
||||
: idMetricRecord(idStr("MachineInfo")) {
|
||||
}
|
||||
|
||||
void idMetricsFramework::MachineInfo::WriteHeader(idMetricFile* metricFile) {
|
||||
FILE* const stream = metricFile == nullptr ? nullptr
|
||||
: static_cast<FILE*>(metricFile->fileHandle);
|
||||
if (stream == nullptr) return;
|
||||
idMetricRecord::WriteHeader(metricFile);
|
||||
WriteU32(stream, 1);
|
||||
WriteString(stream, "BuildInfo");
|
||||
WriteU32(stream, 6);
|
||||
}
|
||||
|
||||
void idMetricsFramework::MachineInfo::SerializeEntry(idMetricFile* metricFile) {
|
||||
FILE* const stream = metricFile == nullptr ? nullptr
|
||||
: static_cast<FILE*>(metricFile->fileHandle);
|
||||
if (stream == nullptr) return;
|
||||
idMetricRecord::SerializeEntry(metricFile);
|
||||
WriteString(stream, "tech5-recovery-pc");
|
||||
}
|
||||
|
||||
idMetricsFramework::idMetricsFramework()
|
||||
: currentPushTime(0), currentWriteTime(0), currentPushFrame(0),
|
||||
lastCheckedPushFrame(0), currentState(0), baseHandle(0),
|
||||
isRecording(false), fileTable{nullptr, 256, 0, 255}, stateStream() {
|
||||
ConfigFor(this);
|
||||
}
|
||||
|
||||
idMetricsFramework::~idMetricsFramework() {
|
||||
MetricsStop();
|
||||
for (frameworkConfig_t& config : frameworkConfigs) {
|
||||
if (config.owner == this) {
|
||||
std::memset(&config, 0, sizeof(config));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t idMetricsFramework::GetCurrentSystemTime() const {
|
||||
constexpr std::uint64_t WINDOWS_TO_UNIX_100NS = 116444736000000000ULL;
|
||||
const std::uint64_t systemTime = Sys_CurrentSystemTime();
|
||||
return systemTime >= WINDOWS_TO_UNIX_100NS
|
||||
? (systemTime - WINDOWS_TO_UNIX_100NS) / 10000ULL : systemTime / 10000ULL;
|
||||
}
|
||||
|
||||
int idMetricsFramework::GetPushFrame() {
|
||||
const std::uint64_t now = GetCurrentSystemTime();
|
||||
const int heartbeat = ConfigFor(this).heartbeatMilliseconds;
|
||||
++lastCheckedPushFrame;
|
||||
if (currentPushTime == 0 || now - currentPushTime >=
|
||||
static_cast<std::uint64_t>(std::max(1, heartbeat))) {
|
||||
currentPushTime = now;
|
||||
currentPushFrame = lastCheckedPushFrame;
|
||||
}
|
||||
return currentPushFrame;
|
||||
}
|
||||
|
||||
void idMetricsFramework::SetOutputDirectory(const char* directory) {
|
||||
frameworkConfig_t& config = ConfigFor(this);
|
||||
std::strncpy(config.directory,
|
||||
directory == nullptr || directory[0] == '\0' ? "metrics" : directory,
|
||||
sizeof(config.directory) - 1);
|
||||
config.directory[sizeof(config.directory) - 1] = '\0';
|
||||
}
|
||||
|
||||
void idMetricsFramework::SetHeartbeatMilliseconds(const int milliseconds) {
|
||||
ConfigFor(this).heartbeatMilliseconds = std::max(1, milliseconds);
|
||||
}
|
||||
|
||||
void idMetricsFramework::MetricsRecord() {
|
||||
isRecording = true;
|
||||
MachineInfo machineInfo;
|
||||
machineInfo.Serialize();
|
||||
}
|
||||
|
||||
void idMetricsFramework::MetricsStop() {
|
||||
if (!isRecording && fileTable.numEntries == 0) return;
|
||||
for (metricSlot_t& slot : metricSlots) {
|
||||
if (slot.owner == this) {
|
||||
if (slot.file.fileHandle != nullptr) {
|
||||
std::fclose(static_cast<FILE*>(slot.file.fileHandle));
|
||||
}
|
||||
slot = metricSlot_t{};
|
||||
}
|
||||
}
|
||||
fileTable.numEntries = 0;
|
||||
isRecording = false;
|
||||
++baseHandle;
|
||||
}
|
||||
|
||||
void idMetricsFramework::OpenMetricFile(const idStr& filename,
|
||||
idMetricFile& metricFile) {
|
||||
frameworkConfig_t& config = ConfigFor(this);
|
||||
CreateDirectoryA(config.directory, nullptr);
|
||||
char safeName[128] = {};
|
||||
SanitizeName(filename.c_str(), safeName, sizeof(safeName));
|
||||
char path[MAX_PATH] = {};
|
||||
std::snprintf(path, sizeof(path), "%s\\%s.metric", config.directory,
|
||||
safeName[0] == '\0' ? "unnamed" : safeName);
|
||||
FILE* stream = nullptr;
|
||||
if (fopen_s(&stream, path, "wb") == 0) metricFile.fileHandle = stream;
|
||||
}
|
||||
|
||||
idMetricFile* idMetricsFramework::GetFileHandle(const idStr& filename) {
|
||||
for (metricSlot_t& slot : metricSlots) {
|
||||
if (slot.owner == this && std::strcmp(slot.name, filename.c_str()) == 0) {
|
||||
return &slot.file;
|
||||
}
|
||||
}
|
||||
for (metricSlot_t& slot : metricSlots) {
|
||||
if (slot.owner == nullptr) {
|
||||
slot.owner = this;
|
||||
std::strncpy(slot.name, filename.c_str(), sizeof(slot.name) - 1);
|
||||
OpenMetricFile(filename, slot.file);
|
||||
if (slot.file.fileHandle == nullptr) {
|
||||
slot = metricSlot_t{};
|
||||
return nullptr;
|
||||
}
|
||||
++fileTable.numEntries;
|
||||
return &slot.file;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "metricrecord.h"
|
||||
|
||||
class idMetricsFramework {
|
||||
public:
|
||||
class MachineInfo : public idMetricRecord {
|
||||
public:
|
||||
MachineInfo();
|
||||
void WriteHeader(idMetricFile* metricFile) override;
|
||||
void SerializeEntry(idMetricFile* metricFile) override;
|
||||
};
|
||||
|
||||
idMetricsFramework();
|
||||
~idMetricsFramework();
|
||||
|
||||
std::uint64_t GetCurrentSystemTime() const;
|
||||
int GetPushFrame();
|
||||
void MetricsRecord();
|
||||
void MetricsStop();
|
||||
idMetricFile* GetFileHandle(const idStr& filename);
|
||||
bool IsRecording() const { return isRecording; }
|
||||
void SetOutputDirectory(const char* directory);
|
||||
void SetHeartbeatMilliseconds(int milliseconds);
|
||||
|
||||
private:
|
||||
void OpenMetricFile(const idStr& filename, idMetricFile& metricFile);
|
||||
|
||||
std::uint64_t currentPushTime;
|
||||
std::uint64_t currentWriteTime;
|
||||
int currentPushFrame;
|
||||
int lastCheckedPushFrame;
|
||||
int currentState;
|
||||
int baseHandle;
|
||||
bool isRecording;
|
||||
|
||||
struct recoveredHashTable_t {
|
||||
void** heads;
|
||||
int tableSize;
|
||||
int numEntries;
|
||||
int tableSizeMask;
|
||||
} fileTable;
|
||||
idMetricFile stateStream;
|
||||
};
|
||||
|
||||
extern idMetricsFramework metricsFrameworkLocal;
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idMetricsFramework::MachineInfo) == 68,
|
||||
"Recovered MachineInfo ABI changed");
|
||||
static_assert(sizeof(idMetricsFramework) == 72,
|
||||
"Recovered idMetricsFramework ABI changed");
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
#include "plog.h"
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
std::int64_t ClockTicks() {
|
||||
LARGE_INTEGER counter = {};
|
||||
QueryPerformanceCounter(&counter);
|
||||
return counter.QuadPart;
|
||||
}
|
||||
|
||||
double TicksPerMillisecond() {
|
||||
static double value = 0.0;
|
||||
if (value == 0.0) {
|
||||
LARGE_INTEGER frequency = {};
|
||||
QueryPerformanceFrequency(&frequency);
|
||||
value = static_cast<double>(frequency.QuadPart) / 1000.0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
float TicksToMilliseconds(const std::int64_t ticks) {
|
||||
return static_cast<float>(static_cast<double>(ticks) / TicksPerMillisecond());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
idPLog pLog;
|
||||
|
||||
idPLog::idPLog()
|
||||
: treeEntries(64), logEntries(64), lastEntry(0), overHeadTicks(-1),
|
||||
groupMask(0) {
|
||||
Clear();
|
||||
}
|
||||
|
||||
void idPLog::Clear() {
|
||||
treeEntries.Clear();
|
||||
logEntries.Clear();
|
||||
logEntry_t* const root = logEntries.Alloc();
|
||||
if (root != nullptr) {
|
||||
root->label = "root";
|
||||
root->parent = 0;
|
||||
root->totalTicks = 0;
|
||||
}
|
||||
lastEntry = 0;
|
||||
}
|
||||
|
||||
void idPLog::EnsureEntries() {
|
||||
logEntries.Reserve(4096);
|
||||
treeEntries.Reserve(4096);
|
||||
}
|
||||
|
||||
std::int64_t idPLog::GetOverHeadTicks() {
|
||||
if (overHeadTicks >= 0) return overHeadTicks;
|
||||
std::int64_t best = INT64_MAX;
|
||||
for (int pass = 0; pass < 4; ++pass) {
|
||||
const std::int64_t start = ClockTicks();
|
||||
for (int index = 0; index < 1000; ++index) {
|
||||
const std::int64_t end = ClockTicks();
|
||||
(void)end;
|
||||
}
|
||||
best = std::min(best, ClockTicks() - start);
|
||||
}
|
||||
overHeadTicks = static_cast<int>(best / 1000);
|
||||
return overHeadTicks;
|
||||
}
|
||||
|
||||
void idPLog::SubtractOverhead() {
|
||||
const std::int64_t overhead = GetOverHeadTicks();
|
||||
for (int index = 1; index < logEntries.Num(); ++index) {
|
||||
const int parent = logEntries[index].parent;
|
||||
if (parent >= 0 && parent < logEntries.Num()) {
|
||||
logEntries[parent].totalTicks = std::max<std::int64_t>(0,
|
||||
logEntries[parent].totalTicks - overhead);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float idPLog::BuildLogData(const float thresholdMS,
|
||||
const std::int64_t adjustByTicks) {
|
||||
treeEntries.Clear();
|
||||
treeEntry_t* const root = treeEntries.Alloc();
|
||||
if (root == nullptr) return 0.0f;
|
||||
root->label = "root";
|
||||
root->depth = 0;
|
||||
root->numHits = 1;
|
||||
root->parent = -1;
|
||||
root->firstChild = -1;
|
||||
root->nextChild = -1;
|
||||
|
||||
for (int index = 1; index < logEntries.Num(); ++index) {
|
||||
const logEntry_t& source = logEntries[index];
|
||||
int treeParent = 0;
|
||||
if (source.parent > 0 && source.parent < index) {
|
||||
const char* const parentLabel = logEntries[source.parent].label;
|
||||
for (int candidate = treeEntries.Num() - 1; candidate > 0; --candidate) {
|
||||
if (treeEntries[candidate].label == parentLabel
|
||||
|| std::strcmp(treeEntries[candidate].label, parentLabel) == 0) {
|
||||
treeParent = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int destination = -1;
|
||||
for (int candidate = 1; candidate < treeEntries.Num(); ++candidate) {
|
||||
if (treeEntries[candidate].parent == treeParent
|
||||
&& (treeEntries[candidate].label == source.label
|
||||
|| std::strcmp(treeEntries[candidate].label, source.label) == 0)) {
|
||||
destination = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (destination < 0) {
|
||||
treeEntry_t* const entry = treeEntries.Alloc();
|
||||
if (entry == nullptr) break;
|
||||
destination = treeEntries.Num() - 1;
|
||||
entry->label = source.label;
|
||||
entry->depth = treeEntries[treeParent].depth + 1;
|
||||
entry->parent = treeParent;
|
||||
entry->firstChild = -1;
|
||||
entry->nextChild = treeEntries[treeParent].firstChild;
|
||||
treeEntries[treeParent].firstChild = destination;
|
||||
}
|
||||
treeEntry_t& entry = treeEntries[destination];
|
||||
const std::int64_t adjusted = std::max<std::int64_t>(0,
|
||||
source.totalTicks - adjustByTicks);
|
||||
entry.totalTicks += adjusted;
|
||||
++entry.numHits;
|
||||
root->totalTicks += adjusted;
|
||||
}
|
||||
|
||||
if (thresholdMS > 0.0f) {
|
||||
// Keep the recovered table intact; the threshold only controls output.
|
||||
}
|
||||
return TicksToMilliseconds(root->totalTicks);
|
||||
}
|
||||
|
||||
void idPLog::ShowUniqueEntries(const float thresholdMS,
|
||||
const std::int64_t adjustByTicks) {
|
||||
BuildLogData(thresholdMS, adjustByTicks);
|
||||
for (int index = 1; index < treeEntries.Num(); ++index) {
|
||||
const treeEntry_t& entry = treeEntries[index];
|
||||
const float milliseconds = TicksToMilliseconds(entry.totalTicks);
|
||||
if (milliseconds >= thresholdMS) {
|
||||
std::printf("%-40s %6d %10.3f ms\n", entry.label,
|
||||
entry.numHits, milliseconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void idPLog::ShowCallGraph(const float thresholdMS,
|
||||
const std::int64_t adjustByTicks) {
|
||||
BuildLogData(thresholdMS, adjustByTicks);
|
||||
for (int index = 1; index < treeEntries.Num(); ++index) {
|
||||
const treeEntry_t& entry = treeEntries[index];
|
||||
const float milliseconds = TicksToMilliseconds(entry.totalTicks);
|
||||
if (milliseconds >= thresholdMS) {
|
||||
std::printf("%*s%s: %.3f ms (%d)\n", entry.depth * 2, "",
|
||||
entry.label, milliseconds, entry.numHits);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
idPLogScope::idPLogScope(idPLog& log, const std::uint64_t mask,
|
||||
const char* label)
|
||||
: logIndex(-1), pLog(&log) {
|
||||
if (!log.IsGroupEnabled(mask)) return;
|
||||
idPLog::logEntry_t* const entry = log.logEntries.Alloc();
|
||||
if (entry == nullptr) return;
|
||||
entry->label = label == nullptr ? "<unnamed>" : label;
|
||||
entry->parent = log.lastEntry;
|
||||
entry->totalTicks = ClockTicks();
|
||||
logIndex = log.logEntries.Num() - 1;
|
||||
log.lastEntry = logIndex;
|
||||
}
|
||||
|
||||
idPLogScope::~idPLogScope() {
|
||||
End();
|
||||
}
|
||||
|
||||
void idPLogScope::End(std::int64_t* totalTicks) {
|
||||
if (logIndex < 0 || pLog == nullptr) return;
|
||||
idPLog::logEntry_t& entry = pLog->logEntries[logIndex];
|
||||
entry.totalTicks = ClockTicks() - entry.totalTicks;
|
||||
if (totalTicks != nullptr) *totalTicks = entry.totalTicks;
|
||||
pLog->lastEntry = entry.parent;
|
||||
logIndex = -1;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include "idlib/containers/recoveredlist.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#pragma pack(push, 4)
|
||||
class idPLog {
|
||||
public:
|
||||
struct treeEntry_t {
|
||||
const char* label;
|
||||
int depth;
|
||||
int numHits;
|
||||
std::int64_t totalTicks;
|
||||
int parent;
|
||||
int firstChild;
|
||||
int nextChild;
|
||||
bool allocationOccured;
|
||||
};
|
||||
|
||||
struct logEntry_t {
|
||||
const char* label;
|
||||
int parent;
|
||||
std::int64_t totalTicks;
|
||||
};
|
||||
|
||||
idPLog();
|
||||
|
||||
void Clear();
|
||||
void EnsureEntries();
|
||||
void SetGroupMask(std::uint64_t mask) { groupMask = mask; }
|
||||
void EnableGroups(std::uint64_t mask) { groupMask |= mask; }
|
||||
void DisableGroups(std::uint64_t mask) { groupMask &= ~mask; }
|
||||
std::uint64_t GetGroupMask() const { return groupMask; }
|
||||
bool IsGroupEnabled(std::uint64_t mask) const { return (groupMask & mask) != 0; }
|
||||
|
||||
std::int64_t GetOverHeadTicks();
|
||||
void SubtractOverhead();
|
||||
float BuildLogData(float thresholdMS = 0.0f,
|
||||
std::int64_t adjustByTicks = 0);
|
||||
void ShowUniqueEntries(float thresholdMS = 0.0f,
|
||||
std::int64_t adjustByTicks = 0);
|
||||
void ShowCallGraph(float thresholdMS = 0.0f,
|
||||
std::int64_t adjustByTicks = 0);
|
||||
|
||||
int NumLogEntries() const { return logEntries.Num(); }
|
||||
int NumTreeEntries() const { return treeEntries.Num(); }
|
||||
const logEntry_t& GetLogEntry(int index) const { return logEntries[index]; }
|
||||
|
||||
private:
|
||||
friend class idPLogScope;
|
||||
|
||||
idRecoveredList<treeEntry_t> treeEntries;
|
||||
idRecoveredList<logEntry_t> logEntries;
|
||||
int lastEntry;
|
||||
int overHeadTicks;
|
||||
std::uint64_t groupMask;
|
||||
};
|
||||
|
||||
class idPLogScope {
|
||||
public:
|
||||
idPLogScope(idPLog& log, std::uint64_t groupMask, const char* label);
|
||||
~idPLogScope();
|
||||
void End(std::int64_t* totalTicks = nullptr);
|
||||
|
||||
private:
|
||||
int logIndex;
|
||||
idPLog* pLog;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
extern idPLog pLog;
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idPLog::treeEntry_t) == 36,
|
||||
"Recovered idPLog tree entry ABI changed");
|
||||
static_assert(sizeof(idPLog::logEntry_t) == 16,
|
||||
"Recovered idPLog log entry ABI changed");
|
||||
static_assert(sizeof(idPLog) == 48, "Recovered idPLog ABI changed");
|
||||
static_assert(sizeof(idPLogScope) == 8, "Recovered idPLogScope ABI changed");
|
||||
#endif
|
||||
@@ -0,0 +1,98 @@
|
||||
#include "mqmessaging.h"
|
||||
|
||||
#ifdef nullptr
|
||||
#undef nullptr
|
||||
#endif
|
||||
#ifdef snprintf
|
||||
#undef snprintf
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
|
||||
idMQGraphiteClient::idMQGraphiteClient(const idStr& exchangeName,
|
||||
const idStr& metricPrefix)
|
||||
: idMQClientThread(), reportTime(0), channel(nullptr), outgoingEvents(),
|
||||
exchange(exchangeName), prefix(metricPrefix), threadMutex() {
|
||||
}
|
||||
|
||||
idMQGraphiteClient::~idMQGraphiteClient() {
|
||||
StopThread(true);
|
||||
for (int index = 0; index < outgoingEvents.Num(); ++index) {
|
||||
std::free(const_cast<char*>(outgoingEvents[index].eventName));
|
||||
}
|
||||
}
|
||||
|
||||
void idMQGraphiteClient::StartMessageSystem() {
|
||||
StartThread("AMQP Graphite Client");
|
||||
}
|
||||
|
||||
void idMQGraphiteClient::PreRun() {
|
||||
channel = connection.GetChannel();
|
||||
if (channel != nullptr) {
|
||||
channel->ExchangeDeclare(exchange, idStr("topic"), true, false);
|
||||
}
|
||||
reportTime = static_cast<int>(GetTickCount()) + 10000;
|
||||
}
|
||||
|
||||
void idMQGraphiteClient::OnThreadTerminate() { channel = nullptr; }
|
||||
|
||||
void idMQGraphiteClient::LogEvent(const idStr& eventName,
|
||||
const float duration) {
|
||||
idScopedCriticalSection lock(threadMutex);
|
||||
for (int index = 0; index < outgoingEvents.Num(); ++index) {
|
||||
graphiteEvent_t& event = outgoingEvents[index];
|
||||
if (_stricmp(event.eventName, eventName.c_str()) == 0) {
|
||||
++event.count;
|
||||
event.duration += duration;
|
||||
event.mean += (duration - event.mean) / event.count;
|
||||
event.mean2 += duration * duration;
|
||||
return;
|
||||
}
|
||||
}
|
||||
graphiteEvent_t event = {};
|
||||
event.eventName = _strdup(eventName.c_str());
|
||||
event.count = 1;
|
||||
event.duration = duration;
|
||||
event.mean = duration;
|
||||
event.mean2 = duration * duration;
|
||||
outgoingEvents.Append(event);
|
||||
}
|
||||
|
||||
void idMQGraphiteClient::ThreadSlice() {
|
||||
if (static_cast<int>(GetTickCount()) < reportTime) {
|
||||
Sleep(10);
|
||||
return;
|
||||
}
|
||||
idMQList<graphiteEvent_t> events;
|
||||
{
|
||||
idScopedCriticalSection lock(threadMutex);
|
||||
events = outgoingEvents;
|
||||
outgoingEvents.Clear();
|
||||
}
|
||||
const long long timestamp = static_cast<long long>(std::time(nullptr));
|
||||
static const char* const names[] = { "rate", "avg", "std" };
|
||||
for (int index = 0; index < events.Num(); ++index) {
|
||||
const graphiteEvent_t& event = events[index];
|
||||
const float values[] = {
|
||||
event.duration,
|
||||
event.mean,
|
||||
event.count == 0 ? 0.0f
|
||||
: std::sqrt(event.mean2 / static_cast<float>(event.count))
|
||||
};
|
||||
for (int metric = 0; metric < 3; ++metric) {
|
||||
char line[2048];
|
||||
const int amount = std::snprintf(line, sizeof(line),
|
||||
"%s.%s.%s %g %lld", prefix.c_str(), event.eventName,
|
||||
names[metric], values[metric], timestamp);
|
||||
if (channel != nullptr && amount > 0) {
|
||||
channel->BasicPublish(exchange, prefix, false, false, line,
|
||||
static_cast<unsigned int>(amount));
|
||||
}
|
||||
}
|
||||
std::free(const_cast<char*>(event.eventName));
|
||||
}
|
||||
reportTime = static_cast<int>(GetTickCount()) + 10000;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "mqmessaging.h"
|
||||
|
||||
idMQCommand::idMQCommand()
|
||||
: method(nullptr), contentHeader(nullptr), complete(false), contentBody(16),
|
||||
remainingBodyBytes(0) {
|
||||
}
|
||||
|
||||
idMQCommand::~idMQCommand() = default;
|
||||
|
||||
void idMQCommand::Reset() {
|
||||
delete method;
|
||||
delete contentHeader;
|
||||
method = nullptr;
|
||||
contentHeader = nullptr;
|
||||
complete = false;
|
||||
contentBody.Clear();
|
||||
remainingBodyBytes = 0;
|
||||
}
|
||||
|
||||
idMQCommandBuilder::idMQCommandBuilder()
|
||||
: state(MQ_CMDSTATE_NEED_METHOD), remainingBytes(0) {
|
||||
}
|
||||
|
||||
MQErrors_t idMQCommandBuilder::HandleFrame(idMQCommand& command,
|
||||
idMQFrame& frame) {
|
||||
if (state == MQ_CMDSTATE_NEED_METHOD) {
|
||||
if (frame.type != AMQP_FRAME_METHOD) return MQERROR_INVALID_FRAME;
|
||||
command.method = idMQChannel::DecodeMethod(frame);
|
||||
if (command.method == nullptr) return MQERROR_UNEXPECTED_METHOD;
|
||||
state = command.method->HasContent()
|
||||
? MQ_CMDSTATE_NEED_HEADER : MQ_CMDSTATE_COMPLETE;
|
||||
} else if (state == MQ_CMDSTATE_NEED_HEADER) {
|
||||
if (frame.type != AMQP_FRAME_HEADER) return MQERROR_INVALID_FRAME;
|
||||
command.contentHeader = idMQChannel::DecodeContentHeader(frame);
|
||||
if (command.contentHeader == nullptr) return MQERROR_UNEXPECTED_CLASS;
|
||||
command.remainingBodyBytes = command.contentHeader->bodyLength;
|
||||
state = command.remainingBodyBytes == 0
|
||||
? MQ_CMDSTATE_COMPLETE : MQ_CMDSTATE_NEED_BODY;
|
||||
} else if (state == MQ_CMDSTATE_NEED_BODY) {
|
||||
if (frame.type != AMQP_FRAME_BODY) return MQERROR_INVALID_FRAME;
|
||||
if (static_cast<std::uint64_t>(frame.data.Num())
|
||||
> command.remainingBodyBytes) return MQERROR_FRAME_OVERFLOW;
|
||||
for (int index = 0; index < frame.data.Num(); ++index) {
|
||||
command.contentBody.Append(frame.data[index]);
|
||||
}
|
||||
command.remainingBodyBytes -= frame.data.Num();
|
||||
if (command.remainingBodyBytes == 0) state = MQ_CMDSTATE_COMPLETE;
|
||||
} else {
|
||||
return MQERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
command.complete = state == MQ_CMDSTATE_COMPLETE;
|
||||
if (command.complete) {
|
||||
state = MQ_CMDSTATE_NEED_METHOD;
|
||||
remainingBytes = 0;
|
||||
}
|
||||
return MQERROR_NONE;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
#include "mqcommon.h"
|
||||
|
||||
#ifdef nullptr
|
||||
#undef nullptr
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
unsigned short Swap16(const unsigned short value) {
|
||||
return static_cast<unsigned short>((value >> 8) | (value << 8));
|
||||
}
|
||||
|
||||
unsigned int Swap32(const unsigned int value) {
|
||||
return ((value & 0x000000FFu) << 24)
|
||||
| ((value & 0x0000FF00u) << 8)
|
||||
| ((value & 0x00FF0000u) >> 8)
|
||||
| ((value & 0xFF000000u) >> 24);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
amqpEndpoint_t::amqpEndpoint_t()
|
||||
: host("localhost"), port(5672), username("guest"), password("guest"),
|
||||
vhost("/"), channelMax(0), frameMax(0), heartbeat(0),
|
||||
nonBlocking(true), silent(true), minorVersion(0), majorVersion(8) {
|
||||
}
|
||||
|
||||
idMQTCP::idMQTCP() = default;
|
||||
idMQTCP::~idMQTCP() { Close(); }
|
||||
|
||||
bool idMQTCP::Connect(const char* host, const unsigned short port,
|
||||
const bool nonBlocking, const bool silent) {
|
||||
return tcp.Connect(host, port, nonBlocking, silent, false);
|
||||
}
|
||||
|
||||
void idMQTCP::Close() { tcp.Close(); }
|
||||
bool idMQTCP::IsOpen() const { return tcp.IsOpen(); }
|
||||
|
||||
int idMQTCP::Read(void* data, const int size, const bool blocking,
|
||||
const int timeoutMS) {
|
||||
return blocking ? tcp.ReadBlocking(data, size, timeoutMS)
|
||||
: tcp.Read(data, size);
|
||||
}
|
||||
|
||||
int idMQTCP::Write(const void* data, const int size, const bool blocking,
|
||||
const int timeoutMS) {
|
||||
return blocking ? tcp.WriteBlocking(data, size, timeoutMS)
|
||||
: tcp.Write(data, size);
|
||||
}
|
||||
|
||||
int idMQTCP::ReadByte(unsigned char& value, const bool blocking,
|
||||
const int timeoutMS) {
|
||||
return Read(&value, sizeof(value), blocking, timeoutMS);
|
||||
}
|
||||
|
||||
int idMQTCP::ReadUInt16(unsigned short& value, const bool blocking,
|
||||
const int timeoutMS) {
|
||||
const int amount = Read(&value, sizeof(value), blocking, timeoutMS);
|
||||
if (amount == sizeof(value)) value = Swap16(value);
|
||||
return amount;
|
||||
}
|
||||
|
||||
int idMQTCP::ReadUInt32(unsigned int& value, const bool blocking,
|
||||
const int timeoutMS) {
|
||||
const int amount = Read(&value, sizeof(value), blocking, timeoutMS);
|
||||
if (amount == sizeof(value)) value = Swap32(value);
|
||||
return amount;
|
||||
}
|
||||
|
||||
idMQBuffer::idMQBuffer() : body(), readPos(0) {}
|
||||
|
||||
void idMQBuffer::Clear() {
|
||||
body.Clear();
|
||||
readPos = 0;
|
||||
}
|
||||
|
||||
void idMQBuffer::WriteData(const void* source, const int size) {
|
||||
if (source == nullptr || size <= 0) return;
|
||||
const unsigned char* bytes = static_cast<const unsigned char*>(source);
|
||||
for (int index = 0; index < size; ++index) body.Append(bytes[index]);
|
||||
}
|
||||
|
||||
void idMQBuffer::WriteUInt16(unsigned short value, const bool bigEndian) {
|
||||
if (bigEndian) value = Swap16(value);
|
||||
WriteData(&value, sizeof(value));
|
||||
}
|
||||
|
||||
void idMQBuffer::WriteUInt32(unsigned int value, const bool bigEndian) {
|
||||
if (bigEndian) value = Swap32(value);
|
||||
WriteData(&value, sizeof(value));
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
#pragma once
|
||||
|
||||
// Recovered AMQP 0-8 surface used by tungsten's telemetry clients. The wire
|
||||
// protocol remains AMQP compatible; only the old Xbox socket/thread plumbing
|
||||
// is replaced by the portable idTCP and Win32 primitives already in idLib.
|
||||
#include "idlib/precompiled.h"
|
||||
#include "idlib/sys/sys_networking.h"
|
||||
|
||||
#ifdef nullptr
|
||||
#undef nullptr
|
||||
#endif
|
||||
#ifdef _stricmp
|
||||
#undef _stricmp
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
|
||||
template<class T>
|
||||
class idMQList {
|
||||
public:
|
||||
explicit idMQList(int initialGranularity = 16)
|
||||
: list(nullptr), num(0), size(0),
|
||||
granularity(static_cast<short>(initialGranularity)), memTag(5),
|
||||
listStatic(0) {}
|
||||
idMQList(const idMQList& other) : idMQList(other.granularity) {
|
||||
*this = other;
|
||||
}
|
||||
~idMQList() { delete[] list; }
|
||||
idMQList& operator=(const idMQList& other) {
|
||||
if (this == &other) return *this;
|
||||
SetNum(other.num);
|
||||
for (int index = 0; index < num; ++index) list[index] = other[index];
|
||||
return *this;
|
||||
}
|
||||
void Clear() { num = 0; }
|
||||
int Num() const { return num; }
|
||||
T* Ptr() { return list; }
|
||||
const T* Ptr() const { return list; }
|
||||
T& operator[](int index) { return list[index]; }
|
||||
const T& operator[](int index) const { return list[index]; }
|
||||
void SetNum(int newNum) {
|
||||
if (newNum > size) Resize(newNum);
|
||||
if (newNum >= 0 && newNum <= size) num = newNum;
|
||||
}
|
||||
int Append(const T& value) {
|
||||
if (num == size && !Resize(num + 1)) return -1;
|
||||
list[num] = value;
|
||||
return num++;
|
||||
}
|
||||
bool RemoveIndex(int index) {
|
||||
if (index < 0 || index >= num) return false;
|
||||
for (int i = index; i + 1 < num; ++i) list[i] = list[i + 1];
|
||||
--num;
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
bool Resize(int required) {
|
||||
const int step = granularity > 0 ? granularity : 16;
|
||||
const int newSize = ((required + step - 1) / step) * step;
|
||||
T* replacement = new (std::nothrow) T[newSize];
|
||||
if (replacement == nullptr) return false;
|
||||
for (int index = 0; index < num; ++index) replacement[index] = list[index];
|
||||
delete[] list;
|
||||
list = replacement;
|
||||
size = newSize;
|
||||
return true;
|
||||
}
|
||||
T* list;
|
||||
int num;
|
||||
int size;
|
||||
short granularity;
|
||||
unsigned char memTag;
|
||||
unsigned char listStatic;
|
||||
};
|
||||
|
||||
class idMQKeyValue {
|
||||
public:
|
||||
idMQKeyValue() : key(), value() {}
|
||||
idMQKeyValue(const char* keyText, const char* valueText)
|
||||
: key(keyText), value(valueText) {}
|
||||
const idStr& GetKey() const { return key; }
|
||||
const idStr& GetValue() const { return value; }
|
||||
idStr key;
|
||||
idStr value;
|
||||
};
|
||||
|
||||
class idMQTable {
|
||||
public:
|
||||
idMQTable() : args(), hash{} {}
|
||||
void Clear() { args.Clear(); }
|
||||
int GetNumKeyVals() const { return args.Num(); }
|
||||
const idMQKeyValue* GetKeyVal(int index) const {
|
||||
return index >= 0 && index < args.Num() ? &args[index] : nullptr;
|
||||
}
|
||||
void Set(const char* key, const char* value) {
|
||||
const char* safeKey = key == nullptr ? "" : key;
|
||||
for (int index = 0; index < args.Num(); ++index) {
|
||||
if (_stricmp(args[index].key.c_str(), safeKey) == 0) {
|
||||
args[index].value = value == nullptr ? "" : value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
args.Append(idMQKeyValue(safeKey, value == nullptr ? "" : value));
|
||||
}
|
||||
const char* GetString(const char* key, const char* defaultValue = "") const {
|
||||
const char* safeKey = key == nullptr ? "" : key;
|
||||
for (int index = 0; index < args.Num(); ++index) {
|
||||
if (_stricmp(args[index].key.c_str(), safeKey) == 0) {
|
||||
return args[index].value.c_str();
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
private:
|
||||
idMQList<idMQKeyValue> args;
|
||||
std::uint32_t hash[8];
|
||||
};
|
||||
|
||||
enum MQErrors_t {
|
||||
MQERROR_NONE = 0,
|
||||
MQERROR_UNKNOWN,
|
||||
MQERROR_INVALID_FRAME,
|
||||
MQERROR_FRAME_OVERFLOW,
|
||||
MQERROR_SESSION_NOT_FOUND,
|
||||
MQERROR_STREAM_ERROR,
|
||||
MQERROR_CONNECTION_START,
|
||||
MQERROR_BAD_SPEC_VERSION,
|
||||
MQERROR_AUTH_FAILED,
|
||||
MQERROR_UNEXPECTED_CLASS,
|
||||
MQERROR_UNEXPECTED_METHOD,
|
||||
MQERROR_SERVER_CLOSED
|
||||
};
|
||||
|
||||
enum MQAssemblerState_t {
|
||||
MQ_CMDSTATE_NEED_METHOD = 0,
|
||||
MQ_CMDSTATE_NEED_HEADER,
|
||||
MQ_CMDSTATE_NEED_BODY,
|
||||
MQ_CMDSTATE_COMPLETE
|
||||
};
|
||||
|
||||
enum amqpClassId_t {
|
||||
AMQP_CLASS_CONNECTION = 10,
|
||||
AMQP_CLASS_CHANNEL = 20,
|
||||
AMQP_CLASS_ACCESS = 30,
|
||||
AMQP_CLASS_EXCHANGE = 40,
|
||||
AMQP_CLASS_QUEUE = 50,
|
||||
AMQP_CLASS_BASIC = 60
|
||||
};
|
||||
|
||||
enum amqpFrameType_t {
|
||||
AMQP_FRAME_METHOD = 1,
|
||||
AMQP_FRAME_HEADER = 2,
|
||||
AMQP_FRAME_BODY = 3,
|
||||
AMQP_FRAME_HEARTBEAT = 8,
|
||||
AMQP_FRAME_END = 0xCE
|
||||
};
|
||||
|
||||
struct amqpEndpoint_t {
|
||||
amqpEndpoint_t();
|
||||
|
||||
idStr host;
|
||||
unsigned short port;
|
||||
idStr username;
|
||||
idStr password;
|
||||
idStr vhost;
|
||||
unsigned short channelMax;
|
||||
unsigned short frameMax;
|
||||
unsigned short heartbeat;
|
||||
bool nonBlocking;
|
||||
bool silent;
|
||||
int minorVersion;
|
||||
int majorVersion;
|
||||
};
|
||||
|
||||
struct amqpShutdownReason_t {
|
||||
amqpShutdownReason_t()
|
||||
: replyCode(0), cId(0), mId(0), error(MQERROR_NONE), replyText("") {}
|
||||
unsigned short replyCode;
|
||||
unsigned short cId;
|
||||
unsigned short mId;
|
||||
MQErrors_t error;
|
||||
const char* replyText;
|
||||
};
|
||||
|
||||
class idMQFrame;
|
||||
|
||||
class idMQMethod {
|
||||
public:
|
||||
virtual ~idMQMethod() = default;
|
||||
virtual int GetClassId() const = 0;
|
||||
virtual int GetMethodId() const = 0;
|
||||
virtual bool HasContent() const { return false; }
|
||||
virtual bool IsAsync() const { return false; }
|
||||
virtual void Populate(idMQFrame& frame) = 0;
|
||||
virtual void ToFrame(idMQFrame& frame) = 0;
|
||||
};
|
||||
|
||||
class idMQFrame {
|
||||
public:
|
||||
idMQFrame();
|
||||
idMQFrame(unsigned char frameType, unsigned short channelNumber);
|
||||
|
||||
void Clear();
|
||||
void ReadData(void* destination, int length);
|
||||
void WriteData(const void* source, int length);
|
||||
unsigned char ReadByte();
|
||||
unsigned short ReadUInt16();
|
||||
unsigned int ReadUInt32();
|
||||
std::uint64_t ReadUInt64();
|
||||
bool ReadBool();
|
||||
idStr ReadShortString();
|
||||
void ReadString(idMQList<unsigned char>& value);
|
||||
void ReadTable(idMQTable& value);
|
||||
bool ReadPropertyPresence();
|
||||
void FinalizeReadPresence();
|
||||
|
||||
void WriteByte(unsigned char value);
|
||||
void WriteUInt16(unsigned short value);
|
||||
void WriteUInt32(unsigned int value);
|
||||
void WriteUInt64(std::uint64_t value);
|
||||
void WriteBool(bool value);
|
||||
void WriteShortString(const idStr& value);
|
||||
void WriteString(const idMQList<unsigned char>& value);
|
||||
void WriteTable(const idMQTable& value);
|
||||
void WritePropertyPresence(bool present);
|
||||
void FinalizeWritePresence();
|
||||
void FlushWriteBitBuffer();
|
||||
void FinalizeFrame();
|
||||
|
||||
int ReadFromStream(class idMQTCP& stream);
|
||||
int WriteToStream(class idMQTCP& stream);
|
||||
|
||||
unsigned char type;
|
||||
unsigned short channel;
|
||||
idMQList<unsigned char> data;
|
||||
int readPos;
|
||||
bool locked;
|
||||
bool resetReadBitBuffer;
|
||||
unsigned char readBitBuffer;
|
||||
unsigned int readBitMask;
|
||||
bool flushWriteBitBuffer;
|
||||
unsigned char writeBitBuffer;
|
||||
unsigned int writeBitMask;
|
||||
unsigned short readPresenceBuffer;
|
||||
unsigned short readPresencePosition;
|
||||
unsigned short writePresenceBuffer;
|
||||
unsigned short writePresencePosition;
|
||||
};
|
||||
|
||||
class idMQTCP {
|
||||
public:
|
||||
idMQTCP();
|
||||
~idMQTCP();
|
||||
bool Connect(const char* host, unsigned short port, bool nonBlocking,
|
||||
bool silent);
|
||||
void Close();
|
||||
bool IsOpen() const;
|
||||
int Read(void* data, int size, bool blocking = true,
|
||||
int timeoutMS = 5000);
|
||||
int Write(const void* data, int size, bool blocking = true,
|
||||
int timeoutMS = 5000);
|
||||
int ReadByte(unsigned char& value, bool blocking = true,
|
||||
int timeoutMS = 5000);
|
||||
int ReadUInt16(unsigned short& value, bool blocking = true,
|
||||
int timeoutMS = 5000);
|
||||
int ReadUInt32(unsigned int& value, bool blocking = true,
|
||||
int timeoutMS = 5000);
|
||||
|
||||
idTCP tcp;
|
||||
};
|
||||
|
||||
class idMQContentHeader {
|
||||
public:
|
||||
idMQContentHeader() : bodyLength(0) {}
|
||||
virtual ~idMQContentHeader() = default;
|
||||
virtual int GetProtocolId() const = 0;
|
||||
virtual void Populate(idMQFrame& frame) = 0;
|
||||
virtual void ToFrame(idMQFrame& frame) = 0;
|
||||
|
||||
std::uint64_t bodyLength;
|
||||
};
|
||||
|
||||
class idMQBuffer {
|
||||
public:
|
||||
idMQBuffer();
|
||||
void Clear();
|
||||
void WriteData(const void* data, int size);
|
||||
void WriteUInt16(unsigned short value, bool bigEndian = true);
|
||||
void WriteUInt32(unsigned int value, bool bigEndian = true);
|
||||
|
||||
idMQList<unsigned char> body;
|
||||
int readPos;
|
||||
};
|
||||
|
||||
class idMQFrameHandler {
|
||||
public:
|
||||
explicit idMQFrameHandler(const amqpEndpoint_t& endpointValue);
|
||||
~idMQFrameHandler();
|
||||
bool Connect();
|
||||
void Close();
|
||||
int SendFrame(idMQFrame& frame);
|
||||
int ReadFrame(idMQFrame& frame);
|
||||
bool SendHeader();
|
||||
|
||||
amqpEndpoint_t endpoint;
|
||||
idSysMutex readLock;
|
||||
idSysMutex writeLock;
|
||||
idMQTCP tcp;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class idDeferredResult {
|
||||
public:
|
||||
idDeferredResult() : value(), filled(false), signal(false) {}
|
||||
|
||||
void SetValue(const T& newValue) {
|
||||
value = newValue;
|
||||
filled = true;
|
||||
signal.Raise();
|
||||
}
|
||||
bool GetValue(T& result) {
|
||||
if (!signal.Wait(5000) || !filled) return false;
|
||||
result = value;
|
||||
filled = false;
|
||||
return true;
|
||||
}
|
||||
bool GetValue() {
|
||||
if (!signal.Wait(5000) || !filled) return false;
|
||||
filled = false;
|
||||
return true;
|
||||
}
|
||||
void Clear() {
|
||||
filled = false;
|
||||
signal.Clear();
|
||||
}
|
||||
|
||||
T value;
|
||||
bool filled;
|
||||
idSysSignal signal;
|
||||
};
|
||||
|
||||
class idMQCommand {
|
||||
public:
|
||||
idMQCommand();
|
||||
~idMQCommand();
|
||||
void Reset();
|
||||
|
||||
idMQMethod* method;
|
||||
idMQContentHeader* contentHeader;
|
||||
bool complete;
|
||||
idMQList<unsigned char> contentBody;
|
||||
std::uint64_t remainingBodyBytes;
|
||||
};
|
||||
|
||||
class idMQCommandBuilder {
|
||||
public:
|
||||
idMQCommandBuilder();
|
||||
MQErrors_t HandleFrame(idMQCommand& command, idMQFrame& frame);
|
||||
|
||||
MQAssemblerState_t state;
|
||||
std::uint64_t remainingBytes;
|
||||
};
|
||||
|
||||
class idMQChannel;
|
||||
class idMQSessionManager;
|
||||
|
||||
class idMQConnection {
|
||||
public:
|
||||
idMQConnection();
|
||||
~idMQConnection();
|
||||
void Init(amqpEndpoint_t& endpoint);
|
||||
bool Connect();
|
||||
void Close();
|
||||
void Close(const char* reason, MQErrors_t error);
|
||||
void Close(amqpShutdownReason_t& reason);
|
||||
void SendFrame(idMQFrame& frame);
|
||||
idMQChannel* GetChannel();
|
||||
|
||||
unsigned int frameMaxSize;
|
||||
idMQList<idStr> knownHosts;
|
||||
idMQFrameHandler* framer;
|
||||
idMQSessionManager* sessionManager;
|
||||
class idMQSession* sessionZero;
|
||||
volatile bool terminateConnection;
|
||||
volatile bool connectionTerminated;
|
||||
volatile bool isClosing;
|
||||
unsigned int threadHandle;
|
||||
amqpShutdownReason_t shutdownReason;
|
||||
|
||||
private:
|
||||
bool OpenCommunications();
|
||||
void CreateThread();
|
||||
void MessageThread();
|
||||
static unsigned int StaticThread(void* connection);
|
||||
};
|
||||
|
||||
class idMQSession {
|
||||
public:
|
||||
idMQSession(int channelNumber, idMQConnection* connection);
|
||||
~idMQSession();
|
||||
void Init(bool connectionChannel = false);
|
||||
void Close();
|
||||
void Close(const char* reason, MQErrors_t error,
|
||||
bool notifyServer = true);
|
||||
void HandleFrame(idMQFrame& frame);
|
||||
void SendCommand(idMQCommand& command);
|
||||
|
||||
idMQConnection* connection;
|
||||
idMQCommandBuilder cmdBuilder;
|
||||
idMQChannel* protocol;
|
||||
idMQCommand currentCommand;
|
||||
int channelNumber;
|
||||
bool isOpen;
|
||||
};
|
||||
|
||||
class idMQSessionManager {
|
||||
public:
|
||||
explicit idMQSessionManager(idMQConnection* connection);
|
||||
~idMQSessionManager();
|
||||
void Init(int maximumSessions);
|
||||
void Shutdown();
|
||||
idMQSession* CreateSession();
|
||||
idMQSession* Lookup(int channelNumber);
|
||||
|
||||
bool initialized;
|
||||
idMQConnection* connection;
|
||||
int maxSessions;
|
||||
idMQList<idMQSession*> sessionList;
|
||||
};
|
||||
|
||||
class idMQClientThread {
|
||||
public:
|
||||
idMQClientThread();
|
||||
virtual ~idMQClientThread();
|
||||
virtual void StartMessageSystem() { StartThread("AMQP Client"); }
|
||||
virtual void StopMessageSystem() { StopThread(true); }
|
||||
virtual void PreRun() {}
|
||||
virtual void ThreadSlice() { Sys_Yield(); }
|
||||
virtual void OnThreadTerminate() {}
|
||||
|
||||
void StartThread(const char* threadName);
|
||||
void StopThread(bool waitForStop);
|
||||
|
||||
unsigned int handle;
|
||||
volatile bool signalQuit;
|
||||
volatile bool terminated;
|
||||
idMQConnection connection;
|
||||
int retryTime;
|
||||
|
||||
protected:
|
||||
static void Connect(idMQClientThread* thread);
|
||||
static unsigned int Thread(void* thread);
|
||||
};
|
||||
|
||||
class idMQClient : public idMQClientThread {};
|
||||
|
||||
class idMQGraphiteClient : public idMQClientThread {
|
||||
public:
|
||||
struct graphiteEvent_t {
|
||||
const char* eventName;
|
||||
int count;
|
||||
float duration;
|
||||
float mean;
|
||||
float mean2;
|
||||
};
|
||||
|
||||
idMQGraphiteClient(const idStr& exchangeName, const idStr& metricPrefix);
|
||||
~idMQGraphiteClient() override;
|
||||
void StartMessageSystem() override;
|
||||
void PreRun() override;
|
||||
void ThreadSlice() override;
|
||||
void OnThreadTerminate() override;
|
||||
void LogEvent(const idStr& eventName, float duration);
|
||||
|
||||
int reportTime;
|
||||
idMQChannel* channel;
|
||||
idMQList<graphiteEvent_t> outgoingEvents;
|
||||
idStr exchange;
|
||||
idStr prefix;
|
||||
idSysMutex threadMutex;
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idMQTCP) == 20, "Recovered idMQTCP ABI changed");
|
||||
static_assert(sizeof(idMQList<unsigned char>) == 16,
|
||||
"Recovered AMQP list ABI changed");
|
||||
static_assert(sizeof(idMQTable) == 48, "Recovered AMQP table ABI changed");
|
||||
static_assert(sizeof(idMQBuffer) == 20, "Recovered idMQBuffer ABI changed");
|
||||
static_assert(sizeof(idMQMethod) == 4, "Recovered idMQMethod ABI changed");
|
||||
static_assert(sizeof(idMQContentHeader) == 16,
|
||||
"Recovered idMQContentHeader ABI changed");
|
||||
#endif
|
||||
@@ -0,0 +1,181 @@
|
||||
#include "mqmessaging.h"
|
||||
|
||||
#ifdef nullptr
|
||||
#undef nullptr
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
|
||||
idMQConnection::idMQConnection()
|
||||
: frameMaxSize(131072), knownHosts(), framer(nullptr),
|
||||
sessionManager(nullptr), sessionZero(nullptr), terminateConnection(false),
|
||||
connectionTerminated(true), isClosing(false), threadHandle(0),
|
||||
shutdownReason() {
|
||||
}
|
||||
|
||||
idMQConnection::~idMQConnection() {
|
||||
Close();
|
||||
delete sessionZero;
|
||||
delete sessionManager;
|
||||
delete framer;
|
||||
}
|
||||
|
||||
void idMQConnection::Init(amqpEndpoint_t& endpoint) {
|
||||
Close();
|
||||
delete sessionZero;
|
||||
delete sessionManager;
|
||||
delete framer;
|
||||
framer = new idMQFrameHandler(endpoint);
|
||||
sessionManager = new idMQSessionManager(this);
|
||||
sessionZero = new idMQSession(0, this);
|
||||
sessionZero->Init(true);
|
||||
frameMaxSize = endpoint.frameMax == 0 ? 131072 : endpoint.frameMax;
|
||||
shutdownReason = amqpShutdownReason_t();
|
||||
}
|
||||
|
||||
void idMQConnection::CreateThread() {
|
||||
terminateConnection = false;
|
||||
connectionTerminated = false;
|
||||
threadHandle = static_cast<unsigned int>(Sys_CreateThread(
|
||||
StaticThread, this, THREAD_LOWEST, "AMQP Connection", CORE_ANY,
|
||||
0x20000, false));
|
||||
}
|
||||
|
||||
unsigned int idMQConnection::StaticThread(void* value) {
|
||||
static_cast<idMQConnection*>(value)->MessageThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void idMQConnection::MessageThread() {
|
||||
while (!terminateConnection && framer != nullptr && framer->tcp.IsOpen()) {
|
||||
idMQFrame frame;
|
||||
if (framer->ReadFrame(frame) < 0) break;
|
||||
idMQSession* session = frame.channel == 0 ? sessionZero
|
||||
: sessionManager == nullptr ? nullptr
|
||||
: sessionManager->Lookup(frame.channel);
|
||||
if (session == nullptr) {
|
||||
shutdownReason.error = MQERROR_SESSION_NOT_FOUND;
|
||||
shutdownReason.replyText = "AMQP session not found";
|
||||
break;
|
||||
}
|
||||
session->HandleFrame(frame);
|
||||
}
|
||||
connectionTerminated = true;
|
||||
}
|
||||
|
||||
bool idMQConnection::OpenCommunications() {
|
||||
if (framer == nullptr || sessionZero == nullptr || sessionZero->protocol == nullptr
|
||||
|| !framer->SendHeader()) return false;
|
||||
AMQPConnectionStart start;
|
||||
if (!sessionZero->protocol->deferredConnectionStart.GetValue(start)) {
|
||||
shutdownReason.error = MQERROR_CONNECTION_START;
|
||||
shutdownReason.replyText = "Unable to get ConnectionStart details";
|
||||
return false;
|
||||
}
|
||||
if (start.versionMajor != 8 || start.versionMinor != 0) {
|
||||
shutdownReason.error = MQERROR_BAD_SPEC_VERSION;
|
||||
shutdownReason.replyText = "AMQP protocol version mismatch";
|
||||
return false;
|
||||
}
|
||||
|
||||
idMQList<unsigned char> response(16);
|
||||
response.Append(0);
|
||||
const char* username = framer->endpoint.username.c_str();
|
||||
for (int i = 0; username[i] != 0; ++i) response.Append(username[i]);
|
||||
response.Append(0);
|
||||
const char* password = framer->endpoint.password.c_str();
|
||||
for (int i = 0; password[i] != 0; ++i) response.Append(password[i]);
|
||||
|
||||
AMQPConnectionTune tune;
|
||||
sessionZero->protocol->ConnectionStartOk(tune, idStr("PLAIN"), response,
|
||||
idStr("en_US"));
|
||||
if (tune.frameMax == 0 && tune.channelMax == 0 && tune.heartbeat == 0) {
|
||||
shutdownReason.error = MQERROR_AUTH_FAILED;
|
||||
shutdownReason.replyText = "AMQP authentication/tuning failed";
|
||||
return false;
|
||||
}
|
||||
const unsigned short channelMax = framer->endpoint.channelMax == 0
|
||||
? tune.channelMax : framer->endpoint.channelMax;
|
||||
const unsigned int frameMax = framer->endpoint.frameMax == 0
|
||||
? tune.frameMax : framer->endpoint.frameMax;
|
||||
const unsigned short heartbeat = framer->endpoint.heartbeat == 0
|
||||
? tune.heartbeat : framer->endpoint.heartbeat;
|
||||
frameMaxSize = frameMax == 0 ? 131072 : frameMax;
|
||||
sessionManager->Init(channelMax);
|
||||
sessionZero->protocol->ConnectionTuneOk(channelMax, frameMax, heartbeat);
|
||||
idStr hosts;
|
||||
sessionZero->protocol->ConnectionOpen(hosts, framer->endpoint.vhost,
|
||||
idStr(""), false);
|
||||
if (connectionTerminated) return false;
|
||||
knownHosts.Clear();
|
||||
const char* cursor = hosts.c_str();
|
||||
while (*cursor != 0) {
|
||||
while (*cursor == ' ') ++cursor;
|
||||
const char* begin = cursor;
|
||||
while (*cursor != 0 && *cursor != ' ') ++cursor;
|
||||
if (cursor != begin) {
|
||||
idStr host;
|
||||
host.Append(begin, static_cast<int>(cursor - begin));
|
||||
knownHosts.Append(host);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool idMQConnection::Connect() {
|
||||
if (framer == nullptr || !framer->Connect()) return false;
|
||||
CreateThread();
|
||||
if (!OpenCommunications()) {
|
||||
Close(shutdownReason);
|
||||
return false;
|
||||
}
|
||||
connectionTerminated = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void idMQConnection::SendFrame(idMQFrame& frame) {
|
||||
if (framer == nullptr || framer->SendFrame(frame) < 0) {
|
||||
shutdownReason.error = MQERROR_STREAM_ERROR;
|
||||
shutdownReason.replyText = "AMQP stream write failed";
|
||||
connectionTerminated = true;
|
||||
}
|
||||
}
|
||||
|
||||
idMQChannel* idMQConnection::GetChannel() {
|
||||
if (sessionManager == nullptr || connectionTerminated) return nullptr;
|
||||
idMQSession* session = sessionManager->CreateSession();
|
||||
return session == nullptr ? nullptr : session->protocol;
|
||||
}
|
||||
|
||||
void idMQConnection::Close(const char* reason, const MQErrors_t error) {
|
||||
amqpShutdownReason_t value;
|
||||
value.error = error;
|
||||
value.replyText = reason == nullptr ? "" : reason;
|
||||
Close(value);
|
||||
}
|
||||
|
||||
void idMQConnection::Close(amqpShutdownReason_t& reason) {
|
||||
if (isClosing) return;
|
||||
isClosing = true;
|
||||
shutdownReason = reason;
|
||||
if (!connectionTerminated && sessionZero != nullptr
|
||||
&& sessionZero->protocol != nullptr && reason.error == MQERROR_NONE) {
|
||||
sessionZero->protocol->ConnectionClose(200, idStr("Goodbye"), 0, 0);
|
||||
}
|
||||
terminateConnection = true;
|
||||
if (framer != nullptr) framer->Close();
|
||||
if (threadHandle != 0
|
||||
&& Sys_GetCurrentThreadID() != static_cast<uintptr_t>(threadHandle)) {
|
||||
Sys_WaitForThread(static_cast<uintptr_t>(threadHandle));
|
||||
Sys_DestroyThread(static_cast<uintptr_t>(threadHandle));
|
||||
}
|
||||
threadHandle = 0;
|
||||
if (sessionManager != nullptr) sessionManager->Shutdown();
|
||||
connectionTerminated = true;
|
||||
isClosing = false;
|
||||
}
|
||||
|
||||
void idMQConnection::Close() {
|
||||
amqpShutdownReason_t reason;
|
||||
Close(reason);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "mqcommon.h"
|
||||
|
||||
class AMQPBasicProperties;
|
||||
|
||||
class idMQConsumer {
|
||||
public:
|
||||
idMQConsumer() : consumerTag(), isConsuming(false), mutex() {}
|
||||
virtual ~idMQConsumer() = default;
|
||||
virtual void ProcessBasicConsumeOk(const idStr& tag) {
|
||||
idScopedCriticalSection lock(mutex);
|
||||
consumerTag = tag;
|
||||
isConsuming = true;
|
||||
}
|
||||
virtual void ProcessBasicCancel(const idStr&) {
|
||||
idScopedCriticalSection lock(mutex);
|
||||
isConsuming = false;
|
||||
}
|
||||
virtual void ProcessBasicCancelOk(const idStr& tag) {
|
||||
ProcessBasicCancel(tag);
|
||||
}
|
||||
virtual void ProcessBasicDeliver(const idStr& consumerTag,
|
||||
std::uint64_t deliveryTag, bool redelivered, const idStr& exchange,
|
||||
const idStr& routingKey, const AMQPBasicProperties* properties,
|
||||
const idMQList<unsigned char>& body) = 0;
|
||||
|
||||
idStr consumerTag;
|
||||
bool isConsuming;
|
||||
idSysMutex mutex;
|
||||
};
|
||||
@@ -0,0 +1,330 @@
|
||||
#include "mqcommon.h"
|
||||
|
||||
#ifdef nullptr
|
||||
#undef nullptr
|
||||
#endif
|
||||
#ifdef snprintf
|
||||
#undef snprintf
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
void StoreUInt32BE(unsigned char* destination, const unsigned int value) {
|
||||
destination[0] = static_cast<unsigned char>(value >> 24);
|
||||
destination[1] = static_cast<unsigned char>(value >> 16);
|
||||
destination[2] = static_cast<unsigned char>(value >> 8);
|
||||
destination[3] = static_cast<unsigned char>(value);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
idMQFrame::idMQFrame()
|
||||
: type(0), channel(0), data(), readPos(0), locked(false),
|
||||
resetReadBitBuffer(true), readBitBuffer(0), readBitMask(0),
|
||||
flushWriteBitBuffer(false), writeBitBuffer(0), writeBitMask(1),
|
||||
readPresenceBuffer(0), readPresencePosition(0), writePresenceBuffer(0),
|
||||
writePresencePosition(0) {
|
||||
}
|
||||
|
||||
idMQFrame::idMQFrame(const unsigned char frameType,
|
||||
const unsigned short channelNumber)
|
||||
: idMQFrame() {
|
||||
type = frameType;
|
||||
channel = channelNumber;
|
||||
}
|
||||
|
||||
void idMQFrame::Clear() {
|
||||
type = 0;
|
||||
channel = 0;
|
||||
data.Clear();
|
||||
readPos = 0;
|
||||
locked = false;
|
||||
resetReadBitBuffer = true;
|
||||
readBitBuffer = 0;
|
||||
readBitMask = 0;
|
||||
flushWriteBitBuffer = false;
|
||||
writeBitBuffer = 0;
|
||||
writeBitMask = 1;
|
||||
readPresenceBuffer = readPresencePosition = 0;
|
||||
writePresenceBuffer = writePresencePosition = 0;
|
||||
}
|
||||
|
||||
void idMQFrame::ReadData(void* destination, const int length) {
|
||||
resetReadBitBuffer = true;
|
||||
if (destination == nullptr || length <= 0) return;
|
||||
const int available = std::max(0, data.Num() - readPos);
|
||||
const int amount = std::min(length, available);
|
||||
if (amount > 0) std::memcpy(destination, data.Ptr() + readPos, amount);
|
||||
if (amount < length) {
|
||||
std::memset(static_cast<unsigned char*>(destination) + amount, 0,
|
||||
length - amount);
|
||||
}
|
||||
readPos += amount;
|
||||
}
|
||||
|
||||
void idMQFrame::FlushWriteBitBuffer() {
|
||||
if (!flushWriteBitBuffer) return;
|
||||
data.Append(writeBitBuffer);
|
||||
flushWriteBitBuffer = false;
|
||||
writeBitBuffer = 0;
|
||||
writeBitMask = 1;
|
||||
}
|
||||
|
||||
void idMQFrame::WriteData(const void* source, const int length) {
|
||||
FlushWriteBitBuffer();
|
||||
if (source == nullptr || length <= 0) return;
|
||||
const unsigned char* bytes = static_cast<const unsigned char*>(source);
|
||||
for (int index = 0; index < length; ++index) data.Append(bytes[index]);
|
||||
}
|
||||
|
||||
unsigned char idMQFrame::ReadByte() {
|
||||
unsigned char value = 0;
|
||||
ReadData(&value, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
unsigned short idMQFrame::ReadUInt16() {
|
||||
unsigned char bytes[2] = {};
|
||||
ReadData(bytes, sizeof(bytes));
|
||||
return static_cast<unsigned short>((bytes[0] << 8) | bytes[1]);
|
||||
}
|
||||
|
||||
unsigned int idMQFrame::ReadUInt32() {
|
||||
unsigned char bytes[4] = {};
|
||||
ReadData(bytes, sizeof(bytes));
|
||||
return (static_cast<unsigned int>(bytes[0]) << 24)
|
||||
| (static_cast<unsigned int>(bytes[1]) << 16)
|
||||
| (static_cast<unsigned int>(bytes[2]) << 8)
|
||||
| static_cast<unsigned int>(bytes[3]);
|
||||
}
|
||||
|
||||
std::uint64_t idMQFrame::ReadUInt64() {
|
||||
const std::uint64_t high = ReadUInt32();
|
||||
const std::uint64_t low = ReadUInt32();
|
||||
return (high << 32) | low;
|
||||
}
|
||||
|
||||
bool idMQFrame::ReadBool() {
|
||||
if (resetReadBitBuffer || readBitMask == 0 || readBitMask > 0x80) {
|
||||
if (readPos >= data.Num()) return false;
|
||||
readBitBuffer = data[readPos++];
|
||||
readBitMask = 1;
|
||||
resetReadBitBuffer = false;
|
||||
}
|
||||
const bool result = (readBitBuffer & readBitMask) != 0;
|
||||
readBitMask <<= 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
idStr idMQFrame::ReadShortString() {
|
||||
const int length = ReadByte();
|
||||
idStr result;
|
||||
for (int index = 0; index < length && readPos < data.Num(); ++index) {
|
||||
result.Append(static_cast<char>(data[readPos++]));
|
||||
}
|
||||
resetReadBitBuffer = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void idMQFrame::ReadString(idMQList<unsigned char>& value) {
|
||||
const unsigned int length = ReadUInt32();
|
||||
value.Clear();
|
||||
for (unsigned int index = 0; index < length && readPos < data.Num();
|
||||
++index) value.Append(data[readPos++]);
|
||||
}
|
||||
|
||||
void idMQFrame::WriteByte(const unsigned char value) {
|
||||
WriteData(&value, sizeof(value));
|
||||
}
|
||||
|
||||
void idMQFrame::WriteUInt16(const unsigned short value) {
|
||||
const unsigned char bytes[2] = {
|
||||
static_cast<unsigned char>(value >> 8),
|
||||
static_cast<unsigned char>(value)
|
||||
};
|
||||
WriteData(bytes, sizeof(bytes));
|
||||
}
|
||||
|
||||
void idMQFrame::WriteUInt32(const unsigned int value) {
|
||||
unsigned char bytes[4];
|
||||
StoreUInt32BE(bytes, value);
|
||||
WriteData(bytes, sizeof(bytes));
|
||||
}
|
||||
|
||||
void idMQFrame::WriteUInt64(const std::uint64_t value) {
|
||||
WriteUInt32(static_cast<unsigned int>(value >> 32));
|
||||
WriteUInt32(static_cast<unsigned int>(value));
|
||||
}
|
||||
|
||||
void idMQFrame::WriteBool(const bool value) {
|
||||
if (writeBitMask == 0 || writeBitMask > 0x80) FlushWriteBitBuffer();
|
||||
if (value) writeBitBuffer |= static_cast<unsigned char>(writeBitMask);
|
||||
flushWriteBitBuffer = true;
|
||||
writeBitMask <<= 1;
|
||||
}
|
||||
|
||||
void idMQFrame::WriteShortString(const idStr& value) {
|
||||
const int length = std::min(value.Length(), 255);
|
||||
WriteByte(static_cast<unsigned char>(length));
|
||||
WriteData(value.c_str(), length);
|
||||
}
|
||||
|
||||
void idMQFrame::WriteString(const idMQList<unsigned char>& value) {
|
||||
WriteUInt32(value.Num());
|
||||
if (value.Num() > 0) WriteData(value.Ptr(), value.Num());
|
||||
}
|
||||
|
||||
void idMQFrame::WriteTable(const idMQTable& value) {
|
||||
FlushWriteBitBuffer();
|
||||
const int lengthOffset = data.Num();
|
||||
WriteUInt32(0);
|
||||
const int tableStart = data.Num();
|
||||
for (int index = 0; index < value.GetNumKeyVals(); ++index) {
|
||||
const idMQKeyValue* entry = value.GetKeyVal(index);
|
||||
WriteShortString(entry->GetKey());
|
||||
WriteByte('S');
|
||||
const idStr& text = entry->GetValue();
|
||||
WriteUInt32(text.Length());
|
||||
WriteData(text.c_str(), text.Length());
|
||||
}
|
||||
StoreUInt32BE(data.Ptr() + lengthOffset, data.Num() - tableStart);
|
||||
}
|
||||
|
||||
void idMQFrame::ReadTable(idMQTable& value) {
|
||||
value.Clear();
|
||||
const unsigned int length = ReadUInt32();
|
||||
const int end = std::min(data.Num(), readPos + static_cast<int>(length));
|
||||
while (readPos < end) {
|
||||
const idStr key = ReadShortString();
|
||||
const unsigned char fieldType = ReadByte();
|
||||
idStr text;
|
||||
if (fieldType == 'S') {
|
||||
const unsigned int stringLength = ReadUInt32();
|
||||
for (unsigned int i = 0; i < stringLength && readPos < end; ++i) {
|
||||
text.Append(static_cast<char>(data[readPos++]));
|
||||
}
|
||||
} else if (fieldType == 't') {
|
||||
text = ReadByte() != 0 ? "1" : "0";
|
||||
} else if (fieldType == 'I') {
|
||||
char buffer[32];
|
||||
std::snprintf(buffer, sizeof(buffer), "%d",
|
||||
static_cast<int>(ReadUInt32()));
|
||||
text = buffer;
|
||||
} else if (fieldType == 'l') {
|
||||
char buffer[32];
|
||||
std::snprintf(buffer, sizeof(buffer), "%llu",
|
||||
static_cast<unsigned long long>(ReadUInt64()));
|
||||
text = buffer;
|
||||
} else if (fieldType == 'V') {
|
||||
text.Clear();
|
||||
} else {
|
||||
// Unknown table values cannot be sized safely without the full
|
||||
// AMQP type grammar, so consume the remaining declared table.
|
||||
readPos = end;
|
||||
break;
|
||||
}
|
||||
value.Set(key.c_str(), text.c_str());
|
||||
}
|
||||
readPos = end;
|
||||
resetReadBitBuffer = true;
|
||||
}
|
||||
|
||||
void idMQFrame::WritePropertyPresence(const bool present) {
|
||||
if (writePresencePosition < 15 && present) {
|
||||
writePresenceBuffer |= static_cast<unsigned short>(
|
||||
0x8000u >> writePresencePosition);
|
||||
}
|
||||
++writePresencePosition;
|
||||
}
|
||||
|
||||
void idMQFrame::FinalizeWritePresence() {
|
||||
WriteUInt16(writePresenceBuffer);
|
||||
writePresenceBuffer = 0;
|
||||
writePresencePosition = 0;
|
||||
}
|
||||
|
||||
bool idMQFrame::ReadPropertyPresence() {
|
||||
if (readPresencePosition == 0) readPresenceBuffer = ReadUInt16();
|
||||
const bool result = readPresencePosition < 15
|
||||
&& (readPresenceBuffer & (0x8000u >> readPresencePosition)) != 0;
|
||||
++readPresencePosition;
|
||||
return result;
|
||||
}
|
||||
|
||||
void idMQFrame::FinalizeReadPresence() {
|
||||
readPresenceBuffer = 0;
|
||||
readPresencePosition = 0;
|
||||
}
|
||||
|
||||
void idMQFrame::FinalizeFrame() { FlushWriteBitBuffer(); }
|
||||
|
||||
int idMQFrame::ReadFromStream(idMQTCP& stream) {
|
||||
Clear();
|
||||
unsigned int payloadSize = 0;
|
||||
if (stream.ReadByte(type) != 1 || stream.ReadUInt16(channel) != 2
|
||||
|| stream.ReadUInt32(payloadSize) != 4) return -1;
|
||||
if (payloadSize > 128u * 1024u * 1024u) return -1;
|
||||
data.SetNum(static_cast<int>(payloadSize));
|
||||
if (payloadSize != 0 && stream.Read(data.Ptr(), payloadSize, true, 5000)
|
||||
!= static_cast<int>(payloadSize)) return -1;
|
||||
unsigned char frameEnd = 0;
|
||||
if (stream.ReadByte(frameEnd) != 1 || frameEnd != AMQP_FRAME_END) return -1;
|
||||
return static_cast<int>(payloadSize) + 8;
|
||||
}
|
||||
|
||||
int idMQFrame::WriteToStream(idMQTCP& stream) {
|
||||
FinalizeFrame();
|
||||
unsigned char header[7] = {
|
||||
type,
|
||||
static_cast<unsigned char>(channel >> 8),
|
||||
static_cast<unsigned char>(channel),
|
||||
0, 0, 0, 0
|
||||
};
|
||||
StoreUInt32BE(header + 3, data.Num());
|
||||
if (stream.Write(header, sizeof(header), true, 5000) != sizeof(header)) {
|
||||
return -1;
|
||||
}
|
||||
if (data.Num() > 0 && stream.Write(data.Ptr(), data.Num(), true, 5000)
|
||||
!= data.Num()) return -1;
|
||||
const unsigned char frameEnd = AMQP_FRAME_END;
|
||||
if (stream.Write(&frameEnd, 1, true, 5000) != 1) return -1;
|
||||
return data.Num() + 8;
|
||||
}
|
||||
|
||||
idMQFrameHandler::idMQFrameHandler(const amqpEndpoint_t& endpointValue)
|
||||
: endpoint(endpointValue), readLock(), writeLock(), tcp() {
|
||||
}
|
||||
|
||||
idMQFrameHandler::~idMQFrameHandler() { Close(); }
|
||||
|
||||
bool idMQFrameHandler::Connect() {
|
||||
return tcp.Connect(endpoint.host.c_str(), endpoint.port,
|
||||
endpoint.nonBlocking, endpoint.silent);
|
||||
}
|
||||
|
||||
void idMQFrameHandler::Close() { tcp.Close(); }
|
||||
|
||||
int idMQFrameHandler::SendFrame(idMQFrame& frame) {
|
||||
idScopedCriticalSection lock(writeLock);
|
||||
return frame.WriteToStream(tcp);
|
||||
}
|
||||
|
||||
int idMQFrameHandler::ReadFrame(idMQFrame& frame) {
|
||||
idScopedCriticalSection lock(readLock);
|
||||
return frame.ReadFromStream(tcp);
|
||||
}
|
||||
|
||||
bool idMQFrameHandler::SendHeader() {
|
||||
const unsigned char protocol[8] = {
|
||||
'A', 'M', 'Q', 'P', 1, 1,
|
||||
static_cast<unsigned char>(endpoint.majorVersion),
|
||||
static_cast<unsigned char>(endpoint.minorVersion)
|
||||
};
|
||||
return tcp.Write(protocol, sizeof(protocol), true, 5000)
|
||||
== sizeof(protocol);
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
#include "mqmessaging.h"
|
||||
|
||||
#ifdef nullptr
|
||||
#undef nullptr
|
||||
#endif
|
||||
|
||||
#define EMPTY_METHOD(name) \
|
||||
void name::Populate(idMQFrame&) {} \
|
||||
void name::ToFrame(idMQFrame&) {}
|
||||
|
||||
void AMQPConnectionStart::Populate(idMQFrame& frame) {
|
||||
versionMajor = frame.ReadByte();
|
||||
versionMinor = frame.ReadByte();
|
||||
frame.ReadTable(serverProperties);
|
||||
frame.ReadString(mechanisms);
|
||||
frame.ReadString(locales);
|
||||
}
|
||||
void AMQPConnectionStart::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteByte(versionMajor);
|
||||
frame.WriteByte(versionMinor);
|
||||
frame.WriteTable(serverProperties);
|
||||
frame.WriteString(mechanisms);
|
||||
frame.WriteString(locales);
|
||||
}
|
||||
|
||||
void AMQPConnectionStartOk::Populate(idMQFrame& frame) {
|
||||
frame.ReadTable(clientProperties);
|
||||
mechanism = frame.ReadShortString();
|
||||
frame.ReadString(response);
|
||||
locale = frame.ReadShortString();
|
||||
}
|
||||
void AMQPConnectionStartOk::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteTable(clientProperties);
|
||||
frame.WriteShortString(mechanism);
|
||||
frame.WriteString(response);
|
||||
frame.WriteShortString(locale);
|
||||
}
|
||||
|
||||
void AMQPConnectionSecure::Populate(idMQFrame& frame) {
|
||||
frame.ReadString(challenge);
|
||||
}
|
||||
void AMQPConnectionSecure::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteString(challenge);
|
||||
}
|
||||
void AMQPConnectionSecureOk::Populate(idMQFrame& frame) {
|
||||
frame.ReadString(response);
|
||||
}
|
||||
void AMQPConnectionSecureOk::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteString(response);
|
||||
}
|
||||
|
||||
void AMQPConnectionTune::Populate(idMQFrame& frame) {
|
||||
channelMax = frame.ReadUInt16();
|
||||
frameMax = frame.ReadUInt32();
|
||||
heartbeat = frame.ReadUInt16();
|
||||
}
|
||||
void AMQPConnectionTune::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt16(channelMax);
|
||||
frame.WriteUInt32(frameMax);
|
||||
frame.WriteUInt16(heartbeat);
|
||||
}
|
||||
void AMQPConnectionTuneOk::Populate(idMQFrame& frame) {
|
||||
channelMax = frame.ReadUInt16();
|
||||
frameMax = frame.ReadUInt32();
|
||||
heartbeat = frame.ReadUInt16();
|
||||
}
|
||||
void AMQPConnectionTuneOk::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt16(channelMax);
|
||||
frame.WriteUInt32(frameMax);
|
||||
frame.WriteUInt16(heartbeat);
|
||||
}
|
||||
|
||||
void AMQPConnectionOpen::Populate(idMQFrame& frame) {
|
||||
virtualHost = frame.ReadShortString();
|
||||
capabilities = frame.ReadShortString();
|
||||
insist = frame.ReadBool();
|
||||
}
|
||||
void AMQPConnectionOpen::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteShortString(virtualHost);
|
||||
frame.WriteShortString(capabilities);
|
||||
frame.WriteBool(insist);
|
||||
}
|
||||
void AMQPConnectionOpenOk::Populate(idMQFrame& frame) {
|
||||
knownHosts = frame.ReadShortString();
|
||||
}
|
||||
void AMQPConnectionOpenOk::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteShortString(knownHosts);
|
||||
}
|
||||
void AMQPConnectionRedirect::Populate(idMQFrame& frame) {
|
||||
host = frame.ReadShortString();
|
||||
knownHosts = frame.ReadShortString();
|
||||
}
|
||||
void AMQPConnectionRedirect::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteShortString(host);
|
||||
frame.WriteShortString(knownHosts);
|
||||
}
|
||||
void AMQPConnectionClose::Populate(idMQFrame& frame) {
|
||||
replyCode = frame.ReadUInt16();
|
||||
replyText = frame.ReadShortString();
|
||||
cId = frame.ReadUInt16();
|
||||
mId = frame.ReadUInt16();
|
||||
}
|
||||
void AMQPConnectionClose::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt16(replyCode);
|
||||
frame.WriteShortString(replyText);
|
||||
frame.WriteUInt16(cId);
|
||||
frame.WriteUInt16(mId);
|
||||
}
|
||||
EMPTY_METHOD(AMQPConnectionCloseOk)
|
||||
|
||||
void AMQPChannelOpen::Populate(idMQFrame& frame) {
|
||||
oob = frame.ReadShortString();
|
||||
}
|
||||
void AMQPChannelOpen::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteShortString(oob);
|
||||
}
|
||||
EMPTY_METHOD(AMQPChannelOpenOk)
|
||||
void AMQPChannelFlow::Populate(idMQFrame& frame) { active = frame.ReadBool(); }
|
||||
void AMQPChannelFlow::ToFrame(idMQFrame& frame) { frame.WriteBool(active); }
|
||||
void AMQPChannelFlowOk::Populate(idMQFrame& frame) { active = frame.ReadBool(); }
|
||||
void AMQPChannelFlowOk::ToFrame(idMQFrame& frame) { frame.WriteBool(active); }
|
||||
void AMQPChannelAlert::Populate(idMQFrame& frame) {
|
||||
replyCode = frame.ReadUInt16();
|
||||
replyText = frame.ReadShortString();
|
||||
frame.ReadTable(details);
|
||||
}
|
||||
void AMQPChannelAlert::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt16(replyCode);
|
||||
frame.WriteShortString(replyText);
|
||||
frame.WriteTable(details);
|
||||
}
|
||||
void AMQPChannelClose::Populate(idMQFrame& frame) {
|
||||
replyCode = frame.ReadUInt16();
|
||||
replyText = frame.ReadShortString();
|
||||
cId = frame.ReadUInt16();
|
||||
mId = frame.ReadUInt16();
|
||||
}
|
||||
void AMQPChannelClose::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt16(replyCode);
|
||||
frame.WriteShortString(replyText);
|
||||
frame.WriteUInt16(cId);
|
||||
frame.WriteUInt16(mId);
|
||||
}
|
||||
EMPTY_METHOD(AMQPChannelCloseOk)
|
||||
|
||||
void AMQPExchangeDeclare::Populate(idMQFrame& frame) {
|
||||
ticket = frame.ReadUInt16();
|
||||
exchange = frame.ReadShortString();
|
||||
type = frame.ReadShortString();
|
||||
passive = frame.ReadBool();
|
||||
durable = frame.ReadBool();
|
||||
autoDelete = frame.ReadBool();
|
||||
isInternal = frame.ReadBool();
|
||||
noWait = frame.ReadBool();
|
||||
frame.ReadTable(arguments);
|
||||
}
|
||||
void AMQPExchangeDeclare::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt16(ticket);
|
||||
frame.WriteShortString(exchange);
|
||||
frame.WriteShortString(type);
|
||||
frame.WriteBool(passive);
|
||||
frame.WriteBool(durable);
|
||||
frame.WriteBool(autoDelete);
|
||||
frame.WriteBool(isInternal);
|
||||
frame.WriteBool(noWait);
|
||||
frame.WriteTable(arguments);
|
||||
}
|
||||
EMPTY_METHOD(AMQPExchangeDeclareOk)
|
||||
void AMQPExchangeDelete::Populate(idMQFrame& frame) {
|
||||
ticket = frame.ReadUInt16();
|
||||
exchange = frame.ReadShortString();
|
||||
unused = frame.ReadBool();
|
||||
noWait = frame.ReadBool();
|
||||
}
|
||||
void AMQPExchangeDelete::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt16(ticket);
|
||||
frame.WriteShortString(exchange);
|
||||
frame.WriteBool(unused);
|
||||
frame.WriteBool(noWait);
|
||||
}
|
||||
EMPTY_METHOD(AMQPExchangeDeleteOk)
|
||||
|
||||
void AMQPQueueDeclare::Populate(idMQFrame& frame) {
|
||||
ticket = frame.ReadUInt16();
|
||||
queue = frame.ReadShortString();
|
||||
passive = frame.ReadBool();
|
||||
durable = frame.ReadBool();
|
||||
exclusive = frame.ReadBool();
|
||||
autoDelete = frame.ReadBool();
|
||||
noWait = frame.ReadBool();
|
||||
frame.ReadTable(arguments);
|
||||
}
|
||||
void AMQPQueueDeclare::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt16(ticket);
|
||||
frame.WriteShortString(queue);
|
||||
frame.WriteBool(passive);
|
||||
frame.WriteBool(durable);
|
||||
frame.WriteBool(exclusive);
|
||||
frame.WriteBool(autoDelete);
|
||||
frame.WriteBool(noWait);
|
||||
frame.WriteTable(arguments);
|
||||
}
|
||||
void AMQPQueueDeclareOk::Populate(idMQFrame& frame) {
|
||||
queue = frame.ReadShortString();
|
||||
messageCount = frame.ReadUInt32();
|
||||
consumerCount = frame.ReadUInt32();
|
||||
}
|
||||
void AMQPQueueDeclareOk::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteShortString(queue);
|
||||
frame.WriteUInt32(messageCount);
|
||||
frame.WriteUInt32(consumerCount);
|
||||
}
|
||||
void AMQPQueueBind::Populate(idMQFrame& frame) {
|
||||
ticket = frame.ReadUInt16();
|
||||
queue = frame.ReadShortString();
|
||||
exchange = frame.ReadShortString();
|
||||
routingKey = frame.ReadShortString();
|
||||
noWait = frame.ReadBool();
|
||||
frame.ReadTable(arguments);
|
||||
}
|
||||
void AMQPQueueBind::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt16(ticket);
|
||||
frame.WriteShortString(queue);
|
||||
frame.WriteShortString(exchange);
|
||||
frame.WriteShortString(routingKey);
|
||||
frame.WriteBool(noWait);
|
||||
frame.WriteTable(arguments);
|
||||
}
|
||||
EMPTY_METHOD(AMQPQueueBindOk)
|
||||
void AMQPQueueUnbind::Populate(idMQFrame& frame) {
|
||||
ticket = frame.ReadUInt16();
|
||||
queue = frame.ReadShortString();
|
||||
exchange = frame.ReadShortString();
|
||||
routingKey = frame.ReadShortString();
|
||||
frame.ReadTable(arguments);
|
||||
}
|
||||
void AMQPQueueUnbind::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt16(ticket);
|
||||
frame.WriteShortString(queue);
|
||||
frame.WriteShortString(exchange);
|
||||
frame.WriteShortString(routingKey);
|
||||
frame.WriteTable(arguments);
|
||||
}
|
||||
EMPTY_METHOD(AMQPQueueUnbindOk)
|
||||
void AMQPQueuePurge::Populate(idMQFrame& frame) {
|
||||
ticket = frame.ReadUInt16();
|
||||
queue = frame.ReadShortString();
|
||||
noWait = frame.ReadBool();
|
||||
}
|
||||
void AMQPQueuePurge::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt16(ticket);
|
||||
frame.WriteShortString(queue);
|
||||
frame.WriteBool(noWait);
|
||||
}
|
||||
void AMQPQueuePurgeOk::Populate(idMQFrame& frame) {
|
||||
messageCount = frame.ReadUInt32();
|
||||
}
|
||||
void AMQPQueuePurgeOk::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt32(messageCount);
|
||||
}
|
||||
void AMQPQueueDelete::Populate(idMQFrame& frame) {
|
||||
ticket = frame.ReadUInt16();
|
||||
queue = frame.ReadShortString();
|
||||
unused = frame.ReadBool();
|
||||
empty = frame.ReadBool();
|
||||
noWait = frame.ReadBool();
|
||||
}
|
||||
void AMQPQueueDelete::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt16(ticket);
|
||||
frame.WriteShortString(queue);
|
||||
frame.WriteBool(unused);
|
||||
frame.WriteBool(empty);
|
||||
frame.WriteBool(noWait);
|
||||
}
|
||||
void AMQPQueueDeleteOk::Populate(idMQFrame& frame) {
|
||||
messageCount = frame.ReadUInt32();
|
||||
}
|
||||
void AMQPQueueDeleteOk::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt32(messageCount);
|
||||
}
|
||||
|
||||
void AMQPBasicQos::Populate(idMQFrame& frame) {
|
||||
prefetchSize = frame.ReadUInt32();
|
||||
prefetchCount = frame.ReadUInt16();
|
||||
isGlobal = frame.ReadBool();
|
||||
}
|
||||
void AMQPBasicQos::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt32(prefetchSize);
|
||||
frame.WriteUInt16(prefetchCount);
|
||||
frame.WriteBool(isGlobal);
|
||||
}
|
||||
EMPTY_METHOD(AMQPBasicQosOk)
|
||||
void AMQPBasicConsume::Populate(idMQFrame& frame) {
|
||||
ticket = frame.ReadUInt16();
|
||||
queue = frame.ReadShortString();
|
||||
consumerTag = frame.ReadShortString();
|
||||
noLocal = frame.ReadBool();
|
||||
noAck = frame.ReadBool();
|
||||
exclusive = frame.ReadBool();
|
||||
noWait = frame.ReadBool();
|
||||
}
|
||||
void AMQPBasicConsume::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt16(ticket);
|
||||
frame.WriteShortString(queue);
|
||||
frame.WriteShortString(consumerTag);
|
||||
frame.WriteBool(noLocal);
|
||||
frame.WriteBool(noAck);
|
||||
frame.WriteBool(exclusive);
|
||||
frame.WriteBool(noWait);
|
||||
}
|
||||
void AMQPBasicConsumeOk::Populate(idMQFrame& frame) {
|
||||
consumerTag = frame.ReadShortString();
|
||||
}
|
||||
void AMQPBasicConsumeOk::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteShortString(consumerTag);
|
||||
}
|
||||
void AMQPBasicCancel::Populate(idMQFrame& frame) {
|
||||
consumerTag = frame.ReadShortString();
|
||||
noWait = frame.ReadBool();
|
||||
}
|
||||
void AMQPBasicCancel::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteShortString(consumerTag);
|
||||
frame.WriteBool(noWait);
|
||||
}
|
||||
void AMQPBasicCancelOk::Populate(idMQFrame& frame) {
|
||||
consumerTag = frame.ReadShortString();
|
||||
}
|
||||
void AMQPBasicCancelOk::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteShortString(consumerTag);
|
||||
}
|
||||
void AMQPBasicPublish::Populate(idMQFrame& frame) {
|
||||
ticket = frame.ReadUInt16();
|
||||
exchange = frame.ReadShortString();
|
||||
routingKey = frame.ReadShortString();
|
||||
mandatory = frame.ReadBool();
|
||||
immediate = frame.ReadBool();
|
||||
}
|
||||
void AMQPBasicPublish::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt16(ticket);
|
||||
frame.WriteShortString(exchange);
|
||||
frame.WriteShortString(routingKey);
|
||||
frame.WriteBool(mandatory);
|
||||
frame.WriteBool(immediate);
|
||||
}
|
||||
void AMQPBasicReturn::Populate(idMQFrame& frame) {
|
||||
replyCode = frame.ReadUInt16();
|
||||
replyText = frame.ReadShortString();
|
||||
exchange = frame.ReadShortString();
|
||||
routingKey = frame.ReadShortString();
|
||||
}
|
||||
void AMQPBasicReturn::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt16(replyCode);
|
||||
frame.WriteShortString(replyText);
|
||||
frame.WriteShortString(exchange);
|
||||
frame.WriteShortString(routingKey);
|
||||
}
|
||||
void AMQPBasicDeliver::Populate(idMQFrame& frame) {
|
||||
consumerTag = frame.ReadShortString();
|
||||
deliveryTag = frame.ReadUInt64();
|
||||
redelivered = frame.ReadBool();
|
||||
exchange = frame.ReadShortString();
|
||||
routingKey = frame.ReadShortString();
|
||||
}
|
||||
void AMQPBasicDeliver::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteShortString(consumerTag);
|
||||
frame.WriteUInt64(deliveryTag);
|
||||
frame.WriteBool(redelivered);
|
||||
frame.WriteShortString(exchange);
|
||||
frame.WriteShortString(routingKey);
|
||||
}
|
||||
void AMQPBasicGet::Populate(idMQFrame& frame) {
|
||||
ticket = frame.ReadUInt16();
|
||||
queue = frame.ReadShortString();
|
||||
noAck = frame.ReadBool();
|
||||
}
|
||||
void AMQPBasicGet::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt16(ticket);
|
||||
frame.WriteShortString(queue);
|
||||
frame.WriteBool(noAck);
|
||||
}
|
||||
void AMQPBasicGetOk::Populate(idMQFrame& frame) {
|
||||
deliveryTag = frame.ReadUInt64();
|
||||
redelivered = frame.ReadBool();
|
||||
exchange = frame.ReadShortString();
|
||||
routingKey = frame.ReadShortString();
|
||||
messageCount = frame.ReadUInt32();
|
||||
}
|
||||
void AMQPBasicGetOk::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt64(deliveryTag);
|
||||
frame.WriteBool(redelivered);
|
||||
frame.WriteShortString(exchange);
|
||||
frame.WriteShortString(routingKey);
|
||||
frame.WriteUInt32(messageCount);
|
||||
}
|
||||
void AMQPBasicGetEmpty::Populate(idMQFrame& frame) {
|
||||
clusterId = frame.ReadShortString();
|
||||
}
|
||||
void AMQPBasicGetEmpty::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteShortString(clusterId);
|
||||
}
|
||||
void AMQPBasicAck::Populate(idMQFrame& frame) {
|
||||
deliveryTag = frame.ReadUInt64();
|
||||
multiple = frame.ReadBool();
|
||||
}
|
||||
void AMQPBasicAck::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt64(deliveryTag);
|
||||
frame.WriteBool(multiple);
|
||||
}
|
||||
void AMQPBasicReject::Populate(idMQFrame& frame) {
|
||||
deliveryTag = frame.ReadUInt64();
|
||||
requeue = frame.ReadBool();
|
||||
}
|
||||
void AMQPBasicReject::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteUInt64(deliveryTag);
|
||||
frame.WriteBool(requeue);
|
||||
}
|
||||
void AMQPBasicRecoverAsync::Populate(idMQFrame& frame) {
|
||||
requeue = frame.ReadBool();
|
||||
}
|
||||
void AMQPBasicRecoverAsync::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteBool(requeue);
|
||||
}
|
||||
void AMQPBasicRecover::Populate(idMQFrame& frame) {
|
||||
requeue = frame.ReadBool();
|
||||
}
|
||||
void AMQPBasicRecover::ToFrame(idMQFrame& frame) {
|
||||
frame.WriteBool(requeue);
|
||||
}
|
||||
EMPTY_METHOD(AMQPBasicRecoverOk)
|
||||
|
||||
AMQPBasicProperties::AMQPBasicProperties()
|
||||
: idMQContentHeader(), contentType(), contentEncoding(), headers(),
|
||||
deliveryMode(0), priority(0), correlationId(), replyTo(), expiration(),
|
||||
messageId(), timestamp(0), type(), userId(), appId(), clusterId(),
|
||||
b_contentType(false), b_contentEncoding(false), b_headers(false),
|
||||
b_deliveryMode(false), b_priority(false), b_correlationId(false),
|
||||
b_replyTo(false), b_expiration(false), b_messageId(false),
|
||||
b_timestamp(false), b_type(false), b_userId(false), b_appId(false),
|
||||
b_clusterId(false) {
|
||||
}
|
||||
|
||||
void AMQPBasicProperties::Populate(idMQFrame& frame) {
|
||||
b_contentType = frame.ReadPropertyPresence();
|
||||
b_contentEncoding = frame.ReadPropertyPresence();
|
||||
b_headers = frame.ReadPropertyPresence();
|
||||
b_deliveryMode = frame.ReadPropertyPresence();
|
||||
b_priority = frame.ReadPropertyPresence();
|
||||
b_correlationId = frame.ReadPropertyPresence();
|
||||
b_replyTo = frame.ReadPropertyPresence();
|
||||
b_expiration = frame.ReadPropertyPresence();
|
||||
b_messageId = frame.ReadPropertyPresence();
|
||||
b_timestamp = frame.ReadPropertyPresence();
|
||||
b_type = frame.ReadPropertyPresence();
|
||||
b_userId = frame.ReadPropertyPresence();
|
||||
b_appId = frame.ReadPropertyPresence();
|
||||
b_clusterId = frame.ReadPropertyPresence();
|
||||
frame.FinalizeReadPresence();
|
||||
if (b_contentType) contentType = frame.ReadShortString();
|
||||
if (b_contentEncoding) contentEncoding = frame.ReadShortString();
|
||||
if (b_headers) frame.ReadTable(headers);
|
||||
if (b_deliveryMode) deliveryMode = frame.ReadByte();
|
||||
if (b_priority) priority = frame.ReadByte();
|
||||
if (b_correlationId) correlationId = frame.ReadShortString();
|
||||
if (b_replyTo) replyTo = frame.ReadShortString();
|
||||
if (b_expiration) expiration = frame.ReadShortString();
|
||||
if (b_messageId) messageId = frame.ReadShortString();
|
||||
if (b_timestamp) timestamp = frame.ReadUInt64();
|
||||
if (b_type) type = frame.ReadShortString();
|
||||
if (b_userId) userId = frame.ReadShortString();
|
||||
if (b_appId) appId = frame.ReadShortString();
|
||||
if (b_clusterId) clusterId = frame.ReadShortString();
|
||||
}
|
||||
|
||||
void AMQPBasicProperties::ToFrame(idMQFrame& frame) {
|
||||
frame.WritePropertyPresence(b_contentType);
|
||||
frame.WritePropertyPresence(b_contentEncoding);
|
||||
frame.WritePropertyPresence(b_headers);
|
||||
frame.WritePropertyPresence(b_deliveryMode);
|
||||
frame.WritePropertyPresence(b_priority);
|
||||
frame.WritePropertyPresence(b_correlationId);
|
||||
frame.WritePropertyPresence(b_replyTo);
|
||||
frame.WritePropertyPresence(b_expiration);
|
||||
frame.WritePropertyPresence(b_messageId);
|
||||
frame.WritePropertyPresence(b_timestamp);
|
||||
frame.WritePropertyPresence(b_type);
|
||||
frame.WritePropertyPresence(b_userId);
|
||||
frame.WritePropertyPresence(b_appId);
|
||||
frame.WritePropertyPresence(b_clusterId);
|
||||
frame.FinalizeWritePresence();
|
||||
if (b_contentType) frame.WriteShortString(contentType);
|
||||
if (b_contentEncoding) frame.WriteShortString(contentEncoding);
|
||||
if (b_headers) frame.WriteTable(headers);
|
||||
if (b_deliveryMode) frame.WriteByte(deliveryMode);
|
||||
if (b_priority) frame.WriteByte(priority);
|
||||
if (b_correlationId) frame.WriteShortString(correlationId);
|
||||
if (b_replyTo) frame.WriteShortString(replyTo);
|
||||
if (b_expiration) frame.WriteShortString(expiration);
|
||||
if (b_messageId) frame.WriteShortString(messageId);
|
||||
if (b_timestamp) frame.WriteUInt64(timestamp);
|
||||
if (b_type) frame.WriteShortString(type);
|
||||
if (b_userId) frame.WriteShortString(userId);
|
||||
if (b_appId) frame.WriteShortString(appId);
|
||||
if (b_clusterId) frame.WriteShortString(clusterId);
|
||||
}
|
||||
|
||||
#undef EMPTY_METHOD
|
||||
@@ -0,0 +1,597 @@
|
||||
#pragma once
|
||||
|
||||
#include "mqcommon.h"
|
||||
|
||||
enum amqpConnectionMethodId_t {
|
||||
AMQP_METHOD_CONNECTION_START = 10,
|
||||
AMQP_METHOD_CONNECTION_STARTOK = 11,
|
||||
AMQP_METHOD_CONNECTION_SECURE = 20,
|
||||
AMQP_METHOD_CONNECTION_SECUREOK = 21,
|
||||
AMQP_METHOD_CONNECTION_TUNE = 30,
|
||||
AMQP_METHOD_CONNECTION_TUNEOK = 31,
|
||||
AMQP_METHOD_CONNECTION_OPEN = 40,
|
||||
AMQP_METHOD_CONNECTION_OPENOK = 41,
|
||||
AMQP_METHOD_CONNECTION_REDIRECT = 50,
|
||||
AMQP_METHOD_CONNECTION_CLOSE = 60,
|
||||
AMQP_METHOD_CONNECTION_CLOSEOK = 61
|
||||
};
|
||||
|
||||
enum amqpChannelMethodId_t {
|
||||
AMQP_METHOD_CHANNEL_OPEN = 10,
|
||||
AMQP_METHOD_CHANNEL_OPENOK = 11,
|
||||
AMQP_METHOD_CHANNEL_FLOW = 20,
|
||||
AMQP_METHOD_CHANNEL_FLOWOK = 21,
|
||||
AMQP_METHOD_CHANNEL_ALERT = 30,
|
||||
AMQP_METHOD_CHANNEL_CLOSE = 40,
|
||||
AMQP_METHOD_CHANNEL_CLOSEOK = 41
|
||||
};
|
||||
|
||||
enum amqpExchangeMethodId_t {
|
||||
AMQP_METHOD_EXCHANGE_DECLARE = 10,
|
||||
AMQP_METHOD_EXCHANGE_DECLAREOK = 11,
|
||||
AMQP_METHOD_EXCHANGE_DELETE = 20,
|
||||
AMQP_METHOD_EXCHANGE_DELETEOK = 21
|
||||
};
|
||||
|
||||
enum amqpQueueMethodId_t {
|
||||
AMQP_METHOD_QUEUE_DECLARE = 10,
|
||||
AMQP_METHOD_QUEUE_DECLAREOK = 11,
|
||||
AMQP_METHOD_QUEUE_BIND = 20,
|
||||
AMQP_METHOD_QUEUE_BINDOK = 21,
|
||||
AMQP_METHOD_QUEUE_PURGE = 30,
|
||||
AMQP_METHOD_QUEUE_PURGEOK = 31,
|
||||
AMQP_METHOD_QUEUE_DELETE = 40,
|
||||
AMQP_METHOD_QUEUE_DELETEOK = 41,
|
||||
AMQP_METHOD_QUEUE_UNBIND = 50,
|
||||
AMQP_METHOD_QUEUE_UNBINDOK = 51
|
||||
};
|
||||
|
||||
enum amqpBasicMethodId_t {
|
||||
AMQP_METHOD_BASIC_QOS = 10,
|
||||
AMQP_METHOD_BASIC_QOSOK = 11,
|
||||
AMQP_METHOD_BASIC_CONSUME = 20,
|
||||
AMQP_METHOD_BASIC_CONSUMEOK = 21,
|
||||
AMQP_METHOD_BASIC_CANCEL = 30,
|
||||
AMQP_METHOD_BASIC_CANCELOK = 31,
|
||||
AMQP_METHOD_BASIC_PUBLISH = 40,
|
||||
AMQP_METHOD_BASIC_RETURN = 50,
|
||||
AMQP_METHOD_BASIC_DELIVER = 60,
|
||||
AMQP_METHOD_BASIC_GET = 70,
|
||||
AMQP_METHOD_BASIC_GETOK = 71,
|
||||
AMQP_METHOD_BASIC_GETEMPTY = 72,
|
||||
AMQP_METHOD_BASIC_ACK = 80,
|
||||
AMQP_METHOD_BASIC_REJECT = 90,
|
||||
AMQP_METHOD_BASIC_RECOVERASYNC = 100,
|
||||
AMQP_METHOD_BASIC_RECOVER = 110,
|
||||
AMQP_METHOD_BASIC_RECOVEROK = 111
|
||||
};
|
||||
|
||||
#define ID_AMQP_METHOD_META(classIdValue, methodIdValue, contentValue, asyncValue) \
|
||||
int GetClassId() const override { return classIdValue; } \
|
||||
int GetMethodId() const override { return methodIdValue; } \
|
||||
bool HasContent() const override { return contentValue; } \
|
||||
bool IsAsync() const override { return asyncValue; } \
|
||||
void Populate(idMQFrame& frame) override; \
|
||||
void ToFrame(idMQFrame& frame) override
|
||||
|
||||
class AMQPConnectionStart : public idMQMethod {
|
||||
public:
|
||||
AMQPConnectionStart() : versionMajor(0), versionMinor(0) {}
|
||||
ID_AMQP_METHOD_META(10, 10, false, true);
|
||||
unsigned char versionMajor;
|
||||
unsigned char versionMinor;
|
||||
idMQTable serverProperties;
|
||||
idMQList<unsigned char> mechanisms;
|
||||
idMQList<unsigned char> locales;
|
||||
};
|
||||
|
||||
class AMQPConnectionStartOk : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(10, 11, false, false);
|
||||
idMQTable clientProperties;
|
||||
idStr mechanism;
|
||||
idMQList<unsigned char> response;
|
||||
idStr locale;
|
||||
};
|
||||
|
||||
class AMQPConnectionSecure : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(10, 20, false, true);
|
||||
idMQList<unsigned char> challenge;
|
||||
};
|
||||
|
||||
class AMQPConnectionSecureOk : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(10, 21, false, false);
|
||||
idMQList<unsigned char> response;
|
||||
};
|
||||
|
||||
class AMQPConnectionTune : public idMQMethod {
|
||||
public:
|
||||
AMQPConnectionTune() : channelMax(0), frameMax(0), heartbeat(0) {}
|
||||
ID_AMQP_METHOD_META(10, 30, false, true);
|
||||
unsigned short channelMax;
|
||||
unsigned int frameMax;
|
||||
unsigned short heartbeat;
|
||||
};
|
||||
|
||||
class AMQPConnectionTuneOk : public idMQMethod {
|
||||
public:
|
||||
AMQPConnectionTuneOk() : channelMax(0), frameMax(0), heartbeat(0) {}
|
||||
ID_AMQP_METHOD_META(10, 31, false, false);
|
||||
unsigned short channelMax;
|
||||
unsigned int frameMax;
|
||||
unsigned short heartbeat;
|
||||
};
|
||||
|
||||
class AMQPConnectionOpen : public idMQMethod {
|
||||
public:
|
||||
AMQPConnectionOpen() : insist(false) {}
|
||||
ID_AMQP_METHOD_META(10, 40, false, false);
|
||||
idStr virtualHost;
|
||||
idStr capabilities;
|
||||
bool insist;
|
||||
};
|
||||
|
||||
class AMQPConnectionOpenOk : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(10, 41, false, true);
|
||||
idStr knownHosts;
|
||||
};
|
||||
|
||||
class AMQPConnectionRedirect : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(10, 50, false, true);
|
||||
idStr host;
|
||||
idStr knownHosts;
|
||||
};
|
||||
|
||||
class AMQPConnectionClose : public idMQMethod {
|
||||
public:
|
||||
AMQPConnectionClose() : replyCode(0), cId(0), mId(0) {}
|
||||
ID_AMQP_METHOD_META(10, 60, false, true);
|
||||
unsigned short replyCode;
|
||||
idStr replyText;
|
||||
unsigned short cId;
|
||||
unsigned short mId;
|
||||
};
|
||||
|
||||
class AMQPConnectionCloseOk : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(10, 61, false, true);
|
||||
};
|
||||
|
||||
class AMQPChannelOpen : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(20, 10, false, false);
|
||||
idStr oob;
|
||||
};
|
||||
|
||||
class AMQPChannelOpenOk : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(20, 11, false, true);
|
||||
};
|
||||
|
||||
class AMQPChannelFlow : public idMQMethod {
|
||||
public:
|
||||
AMQPChannelFlow() : active(false) {}
|
||||
ID_AMQP_METHOD_META(20, 20, false, true);
|
||||
bool active;
|
||||
};
|
||||
|
||||
class AMQPChannelFlowOk : public idMQMethod {
|
||||
public:
|
||||
AMQPChannelFlowOk() : active(false) {}
|
||||
ID_AMQP_METHOD_META(20, 21, false, true);
|
||||
bool active;
|
||||
};
|
||||
|
||||
class AMQPChannelAlert : public idMQMethod {
|
||||
public:
|
||||
AMQPChannelAlert() : replyCode(0) {}
|
||||
ID_AMQP_METHOD_META(20, 30, false, true);
|
||||
unsigned short replyCode;
|
||||
idStr replyText;
|
||||
idMQTable details;
|
||||
};
|
||||
|
||||
class AMQPChannelClose : public idMQMethod {
|
||||
public:
|
||||
AMQPChannelClose() : replyCode(0), cId(0), mId(0) {}
|
||||
ID_AMQP_METHOD_META(20, 40, false, true);
|
||||
unsigned short replyCode;
|
||||
idStr replyText;
|
||||
unsigned short cId;
|
||||
unsigned short mId;
|
||||
};
|
||||
|
||||
class AMQPChannelCloseOk : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(20, 41, false, true);
|
||||
};
|
||||
|
||||
class AMQPExchangeDeclare : public idMQMethod {
|
||||
public:
|
||||
AMQPExchangeDeclare() : ticket(0), passive(false), durable(false),
|
||||
autoDelete(false), isInternal(false), noWait(false) {}
|
||||
ID_AMQP_METHOD_META(40, 10, false, false);
|
||||
unsigned short ticket;
|
||||
idStr exchange;
|
||||
idStr type;
|
||||
bool passive;
|
||||
bool durable;
|
||||
bool autoDelete;
|
||||
bool isInternal;
|
||||
bool noWait;
|
||||
idMQTable arguments;
|
||||
};
|
||||
|
||||
class AMQPExchangeDeclareOk : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(40, 11, false, true);
|
||||
};
|
||||
|
||||
class AMQPExchangeDelete : public idMQMethod {
|
||||
public:
|
||||
AMQPExchangeDelete() : ticket(0), unused(false), noWait(false) {}
|
||||
ID_AMQP_METHOD_META(40, 20, false, false);
|
||||
unsigned short ticket;
|
||||
idStr exchange;
|
||||
bool unused;
|
||||
bool noWait;
|
||||
};
|
||||
|
||||
class AMQPExchangeDeleteOk : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(40, 21, false, true);
|
||||
};
|
||||
|
||||
class AMQPQueueDeclare : public idMQMethod {
|
||||
public:
|
||||
AMQPQueueDeclare() : ticket(0), passive(false), durable(false),
|
||||
exclusive(false), autoDelete(false), noWait(false) {}
|
||||
ID_AMQP_METHOD_META(50, 10, false, false);
|
||||
unsigned short ticket;
|
||||
idStr queue;
|
||||
bool passive;
|
||||
bool durable;
|
||||
bool exclusive;
|
||||
bool autoDelete;
|
||||
bool noWait;
|
||||
idMQTable arguments;
|
||||
};
|
||||
|
||||
class AMQPQueueDeclareOk : public idMQMethod {
|
||||
public:
|
||||
AMQPQueueDeclareOk() : messageCount(0), consumerCount(0) {}
|
||||
ID_AMQP_METHOD_META(50, 11, false, true);
|
||||
idStr queue;
|
||||
unsigned int messageCount;
|
||||
unsigned int consumerCount;
|
||||
};
|
||||
|
||||
class AMQPQueueBind : public idMQMethod {
|
||||
public:
|
||||
AMQPQueueBind() : ticket(0), noWait(false) {}
|
||||
ID_AMQP_METHOD_META(50, 20, false, false);
|
||||
unsigned short ticket;
|
||||
idStr queue;
|
||||
idStr exchange;
|
||||
idStr routingKey;
|
||||
bool noWait;
|
||||
idMQTable arguments;
|
||||
};
|
||||
|
||||
class AMQPQueueBindOk : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(50, 21, false, true);
|
||||
};
|
||||
|
||||
class AMQPQueueUnbind : public idMQMethod {
|
||||
public:
|
||||
AMQPQueueUnbind() : ticket(0) {}
|
||||
ID_AMQP_METHOD_META(50, 50, false, false);
|
||||
unsigned short ticket;
|
||||
idStr queue;
|
||||
idStr exchange;
|
||||
idStr routingKey;
|
||||
idMQTable arguments;
|
||||
};
|
||||
|
||||
class AMQPQueueUnbindOk : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(50, 51, false, true);
|
||||
};
|
||||
|
||||
class AMQPQueuePurge : public idMQMethod {
|
||||
public:
|
||||
AMQPQueuePurge() : ticket(0), noWait(false) {}
|
||||
ID_AMQP_METHOD_META(50, 30, false, false);
|
||||
unsigned short ticket;
|
||||
idStr queue;
|
||||
bool noWait;
|
||||
};
|
||||
|
||||
class AMQPQueuePurgeOk : public idMQMethod {
|
||||
public:
|
||||
AMQPQueuePurgeOk() : messageCount(0) {}
|
||||
ID_AMQP_METHOD_META(50, 31, false, true);
|
||||
unsigned int messageCount;
|
||||
};
|
||||
|
||||
class AMQPQueueDelete : public idMQMethod {
|
||||
public:
|
||||
AMQPQueueDelete() : ticket(0), unused(false), empty(false), noWait(false) {}
|
||||
ID_AMQP_METHOD_META(50, 40, false, false);
|
||||
unsigned short ticket;
|
||||
idStr queue;
|
||||
bool unused;
|
||||
bool empty;
|
||||
bool noWait;
|
||||
};
|
||||
|
||||
class AMQPQueueDeleteOk : public idMQMethod {
|
||||
public:
|
||||
AMQPQueueDeleteOk() : messageCount(0) {}
|
||||
ID_AMQP_METHOD_META(50, 41, false, true);
|
||||
unsigned int messageCount;
|
||||
};
|
||||
|
||||
class AMQPBasicQos : public idMQMethod {
|
||||
public:
|
||||
AMQPBasicQos() : prefetchSize(0), prefetchCount(0), isGlobal(false) {}
|
||||
ID_AMQP_METHOD_META(60, 10, false, false);
|
||||
unsigned int prefetchSize;
|
||||
unsigned short prefetchCount;
|
||||
bool isGlobal;
|
||||
};
|
||||
|
||||
class AMQPBasicQosOk : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(60, 11, false, true);
|
||||
};
|
||||
|
||||
class AMQPBasicConsume : public idMQMethod {
|
||||
public:
|
||||
AMQPBasicConsume() : ticket(0), noLocal(false), noAck(false),
|
||||
exclusive(false), noWait(false) {}
|
||||
ID_AMQP_METHOD_META(60, 20, false, false);
|
||||
unsigned short ticket;
|
||||
idStr queue;
|
||||
idStr consumerTag;
|
||||
bool noLocal;
|
||||
bool noAck;
|
||||
bool exclusive;
|
||||
bool noWait;
|
||||
};
|
||||
|
||||
class AMQPBasicConsumeOk : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(60, 21, false, true);
|
||||
idStr consumerTag;
|
||||
};
|
||||
|
||||
class AMQPBasicCancel : public idMQMethod {
|
||||
public:
|
||||
AMQPBasicCancel() : noWait(false) {}
|
||||
ID_AMQP_METHOD_META(60, 30, false, false);
|
||||
idStr consumerTag;
|
||||
bool noWait;
|
||||
};
|
||||
|
||||
class AMQPBasicCancelOk : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(60, 31, false, true);
|
||||
idStr consumerTag;
|
||||
};
|
||||
|
||||
class AMQPBasicPublish : public idMQMethod {
|
||||
public:
|
||||
AMQPBasicPublish() : ticket(0), mandatory(false), immediate(false) {}
|
||||
ID_AMQP_METHOD_META(60, 40, true, false);
|
||||
unsigned short ticket;
|
||||
idStr exchange;
|
||||
idStr routingKey;
|
||||
bool mandatory;
|
||||
bool immediate;
|
||||
};
|
||||
|
||||
class AMQPBasicReturn : public idMQMethod {
|
||||
public:
|
||||
AMQPBasicReturn() : replyCode(0) {}
|
||||
ID_AMQP_METHOD_META(60, 50, true, true);
|
||||
unsigned short replyCode;
|
||||
idStr replyText;
|
||||
idStr exchange;
|
||||
idStr routingKey;
|
||||
};
|
||||
|
||||
class AMQPBasicDeliver : public idMQMethod {
|
||||
public:
|
||||
AMQPBasicDeliver() : deliveryTag(0), redelivered(false) {}
|
||||
ID_AMQP_METHOD_META(60, 60, true, true);
|
||||
idStr consumerTag;
|
||||
std::uint64_t deliveryTag;
|
||||
bool redelivered;
|
||||
idStr exchange;
|
||||
idStr routingKey;
|
||||
};
|
||||
|
||||
class AMQPBasicGet : public idMQMethod {
|
||||
public:
|
||||
AMQPBasicGet() : ticket(0), noAck(false) {}
|
||||
ID_AMQP_METHOD_META(60, 70, false, false);
|
||||
unsigned short ticket;
|
||||
idStr queue;
|
||||
bool noAck;
|
||||
};
|
||||
|
||||
class AMQPBasicGetOk : public idMQMethod {
|
||||
public:
|
||||
AMQPBasicGetOk() : deliveryTag(0), redelivered(false), messageCount(0) {}
|
||||
ID_AMQP_METHOD_META(60, 71, true, true);
|
||||
std::uint64_t deliveryTag;
|
||||
bool redelivered;
|
||||
idStr exchange;
|
||||
idStr routingKey;
|
||||
unsigned int messageCount;
|
||||
};
|
||||
|
||||
class AMQPBasicGetEmpty : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(60, 72, false, true);
|
||||
idStr clusterId;
|
||||
};
|
||||
|
||||
class AMQPBasicAck : public idMQMethod {
|
||||
public:
|
||||
AMQPBasicAck() : deliveryTag(0), multiple(false) {}
|
||||
ID_AMQP_METHOD_META(60, 80, false, true);
|
||||
std::uint64_t deliveryTag;
|
||||
bool multiple;
|
||||
};
|
||||
|
||||
class AMQPBasicReject : public idMQMethod {
|
||||
public:
|
||||
AMQPBasicReject() : deliveryTag(0), requeue(false) {}
|
||||
ID_AMQP_METHOD_META(60, 90, false, true);
|
||||
std::uint64_t deliveryTag;
|
||||
bool requeue;
|
||||
};
|
||||
|
||||
class AMQPBasicRecoverAsync : public idMQMethod {
|
||||
public:
|
||||
AMQPBasicRecoverAsync() : requeue(false) {}
|
||||
ID_AMQP_METHOD_META(60, 100, false, false);
|
||||
bool requeue;
|
||||
};
|
||||
|
||||
class AMQPBasicRecover : public idMQMethod {
|
||||
public:
|
||||
AMQPBasicRecover() : requeue(false) {}
|
||||
ID_AMQP_METHOD_META(60, 110, false, false);
|
||||
bool requeue;
|
||||
};
|
||||
|
||||
class AMQPBasicRecoverOk : public idMQMethod {
|
||||
public:
|
||||
ID_AMQP_METHOD_META(60, 111, false, true);
|
||||
};
|
||||
|
||||
class AMQPBasicProperties : public idMQContentHeader {
|
||||
public:
|
||||
AMQPBasicProperties();
|
||||
int GetProtocolId() const override { return AMQP_CLASS_BASIC; }
|
||||
void Populate(idMQFrame& frame) override;
|
||||
void ToFrame(idMQFrame& frame) override;
|
||||
|
||||
idStr contentType;
|
||||
idStr contentEncoding;
|
||||
idMQTable headers;
|
||||
unsigned char deliveryMode;
|
||||
unsigned char priority;
|
||||
idStr correlationId;
|
||||
idStr replyTo;
|
||||
idStr expiration;
|
||||
idStr messageId;
|
||||
std::uint64_t timestamp;
|
||||
idStr type;
|
||||
idStr userId;
|
||||
idStr appId;
|
||||
idStr clusterId;
|
||||
bool b_contentType;
|
||||
bool b_contentEncoding;
|
||||
bool b_headers;
|
||||
bool b_deliveryMode;
|
||||
bool b_priority;
|
||||
bool b_correlationId;
|
||||
bool b_replyTo;
|
||||
bool b_expiration;
|
||||
bool b_messageId;
|
||||
bool b_timestamp;
|
||||
bool b_type;
|
||||
bool b_userId;
|
||||
bool b_appId;
|
||||
bool b_clusterId;
|
||||
};
|
||||
|
||||
class idMQConsumer;
|
||||
|
||||
struct amqpBasicGetResultOk_t {
|
||||
amqpBasicGetResultOk_t()
|
||||
: deliveryTag(0), redelivered(false), exchange(), routingKey(),
|
||||
messageCount(0), header(nullptr), body() {}
|
||||
amqpBasicGetResultOk_t(const amqpBasicGetResultOk_t& other);
|
||||
amqpBasicGetResultOk_t& operator=(const amqpBasicGetResultOk_t& other);
|
||||
~amqpBasicGetResultOk_t();
|
||||
std::uint64_t deliveryTag;
|
||||
bool redelivered;
|
||||
idStr exchange;
|
||||
idStr routingKey;
|
||||
unsigned int messageCount;
|
||||
AMQPBasicProperties* header;
|
||||
idMQList<unsigned char> body;
|
||||
};
|
||||
|
||||
class idMQChannel {
|
||||
public:
|
||||
explicit idMQChannel(idMQSession* session);
|
||||
~idMQChannel();
|
||||
void Init();
|
||||
void Close();
|
||||
MQErrors_t HandleCommand(idMQCommand& command);
|
||||
|
||||
static idMQMethod* DecodeMethod(idMQFrame& frame);
|
||||
static idMQContentHeader* DecodeContentHeader(idMQFrame& frame);
|
||||
|
||||
void ConnectionStartOk(AMQPConnectionTune& tune,
|
||||
const idStr& mechanism, const idMQList<unsigned char>& response,
|
||||
const idStr& locale);
|
||||
void ConnectionTuneOk(unsigned short channelMax, unsigned int frameMax,
|
||||
unsigned short heartbeat);
|
||||
void ConnectionOpen(idStr& knownHosts, const idStr& vhost,
|
||||
const idStr& capabilities, bool insist);
|
||||
void ConnectionClose(unsigned short replyCode, const idStr& replyText,
|
||||
unsigned short classId, unsigned short methodId);
|
||||
void ChannelOpen(const idStr& outOfBand = idStr(""));
|
||||
void ExchangeDeclare(const idStr& exchange, const idStr& type,
|
||||
bool durable);
|
||||
void ExchangeDeclare(const idStr& exchange, const idStr& type,
|
||||
bool durable, bool autoDelete);
|
||||
void BasicPublish(const idStr& exchange, const idStr& routingKey,
|
||||
const idMQList<unsigned char>& body);
|
||||
void BasicPublish(const idStr& exchange, const idStr& routingKey,
|
||||
bool mandatory, bool immediate, const void* data, unsigned int size);
|
||||
|
||||
idMQSession* session;
|
||||
idSysMutex protocolWait;
|
||||
idDeferredResult<AMQPConnectionStart> deferredConnectionStart;
|
||||
idDeferredResult<AMQPConnectionTune> deferredConnectionTune;
|
||||
idDeferredResult<AMQPConnectionOpenOk> deferredConnectionOpenOk;
|
||||
idDeferredResult<AMQPChannelOpenOk> deferredChannelOpenOk;
|
||||
idDeferredResult<AMQPChannelFlow> deferredChannelFlow;
|
||||
idDeferredResult<AMQPChannelFlowOk> deferredChannelFlowOk;
|
||||
idDeferredResult<AMQPExchangeDeclareOk> deferredExchangeDeclareOk;
|
||||
idDeferredResult<AMQPExchangeDeleteOk> deferredExchangeDeleteOk;
|
||||
idDeferredResult<AMQPQueueDeclareOk> deferredQueueDeclareOk;
|
||||
idDeferredResult<AMQPQueueBindOk> deferredQueueBindOk;
|
||||
idDeferredResult<AMQPQueuePurgeOk> deferredQueuePurgeOk;
|
||||
idDeferredResult<AMQPQueueDeleteOk> deferredQueueDeleteOk;
|
||||
idDeferredResult<AMQPBasicConsumeOk> deferredBasicConsumeOk;
|
||||
idDeferredResult<AMQPBasicCancelOk> deferredBasicCancelOk;
|
||||
idDeferredResult<amqpBasicGetResultOk_t> deferredBasicGetResultOk;
|
||||
idDeferredResult<AMQPBasicRecoverOk> deferredAMQPBasicRecoverOk;
|
||||
idMQList<idMQConsumer*> consumers;
|
||||
|
||||
private:
|
||||
void ExchangeDeclareInternal(const idStr& exchange, const idStr& type,
|
||||
bool passive, bool durable, bool autoDelete, bool internal,
|
||||
bool noWait);
|
||||
void BasicPublishInternal(const idStr& exchange, const idStr& routingKey,
|
||||
bool mandatory, bool immediate,
|
||||
const idMQList<unsigned char>& body);
|
||||
idMQConsumer* LookupConsumer(const idStr& tag);
|
||||
bool RemoveConsumer(const idStr& tag);
|
||||
};
|
||||
|
||||
#undef ID_AMQP_METHOD_META
|
||||
@@ -0,0 +1,421 @@
|
||||
#include "mqmessaging.h"
|
||||
#include "mqconsumer.h"
|
||||
|
||||
#ifdef nullptr
|
||||
#undef nullptr
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
template<class T>
|
||||
idMQMethod* Decode(idMQFrame& frame) {
|
||||
T* value = new T();
|
||||
value->Populate(frame);
|
||||
return value;
|
||||
}
|
||||
|
||||
void SendMethod(idMQSession* session, idMQMethod& method) {
|
||||
if (session == nullptr) return;
|
||||
idMQCommand command;
|
||||
command.method = &method;
|
||||
session->SendCommand(command);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
amqpBasicGetResultOk_t::amqpBasicGetResultOk_t(
|
||||
const amqpBasicGetResultOk_t& other)
|
||||
: amqpBasicGetResultOk_t() {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
amqpBasicGetResultOk_t& amqpBasicGetResultOk_t::operator=(
|
||||
const amqpBasicGetResultOk_t& other) {
|
||||
if (this == &other) return *this;
|
||||
deliveryTag = other.deliveryTag;
|
||||
redelivered = other.redelivered;
|
||||
exchange = other.exchange;
|
||||
routingKey = other.routingKey;
|
||||
messageCount = other.messageCount;
|
||||
delete header;
|
||||
header = other.header == nullptr ? nullptr
|
||||
: new AMQPBasicProperties(*other.header);
|
||||
body = other.body;
|
||||
return *this;
|
||||
}
|
||||
|
||||
amqpBasicGetResultOk_t::~amqpBasicGetResultOk_t() { delete header; }
|
||||
|
||||
idMQChannel::idMQChannel(idMQSession* sessionValue)
|
||||
: session(sessionValue), protocolWait(), deferredConnectionStart(),
|
||||
deferredConnectionTune(), deferredConnectionOpenOk(),
|
||||
deferredChannelOpenOk(), deferredChannelFlow(), deferredChannelFlowOk(),
|
||||
deferredExchangeDeclareOk(), deferredExchangeDeleteOk(),
|
||||
deferredQueueDeclareOk(), deferredQueueBindOk(), deferredQueuePurgeOk(),
|
||||
deferredQueueDeleteOk(), deferredBasicConsumeOk(),
|
||||
deferredBasicCancelOk(), deferredBasicGetResultOk(),
|
||||
deferredAMQPBasicRecoverOk(), consumers() {
|
||||
}
|
||||
|
||||
idMQChannel::~idMQChannel() = default;
|
||||
|
||||
void idMQChannel::Init() {
|
||||
deferredConnectionStart.Clear();
|
||||
deferredConnectionTune.Clear();
|
||||
deferredConnectionOpenOk.Clear();
|
||||
deferredChannelOpenOk.Clear();
|
||||
}
|
||||
|
||||
idMQMethod* idMQChannel::DecodeMethod(idMQFrame& frame) {
|
||||
const int classId = frame.ReadUInt16();
|
||||
const int methodId = frame.ReadUInt16();
|
||||
switch (classId) {
|
||||
case AMQP_CLASS_CONNECTION:
|
||||
switch (methodId) {
|
||||
case 10: return Decode<AMQPConnectionStart>(frame);
|
||||
case 11: return Decode<AMQPConnectionStartOk>(frame);
|
||||
case 20: return Decode<AMQPConnectionSecure>(frame);
|
||||
case 21: return Decode<AMQPConnectionSecureOk>(frame);
|
||||
case 30: return Decode<AMQPConnectionTune>(frame);
|
||||
case 31: return Decode<AMQPConnectionTuneOk>(frame);
|
||||
case 40: return Decode<AMQPConnectionOpen>(frame);
|
||||
case 41: return Decode<AMQPConnectionOpenOk>(frame);
|
||||
case 50: return Decode<AMQPConnectionRedirect>(frame);
|
||||
case 60: return Decode<AMQPConnectionClose>(frame);
|
||||
case 61: return Decode<AMQPConnectionCloseOk>(frame);
|
||||
}
|
||||
break;
|
||||
case AMQP_CLASS_CHANNEL:
|
||||
switch (methodId) {
|
||||
case 10: return Decode<AMQPChannelOpen>(frame);
|
||||
case 11: return Decode<AMQPChannelOpenOk>(frame);
|
||||
case 20: return Decode<AMQPChannelFlow>(frame);
|
||||
case 21: return Decode<AMQPChannelFlowOk>(frame);
|
||||
case 30: return Decode<AMQPChannelAlert>(frame);
|
||||
case 40: return Decode<AMQPChannelClose>(frame);
|
||||
case 41: return Decode<AMQPChannelCloseOk>(frame);
|
||||
}
|
||||
break;
|
||||
case AMQP_CLASS_EXCHANGE:
|
||||
switch (methodId) {
|
||||
case 10: return Decode<AMQPExchangeDeclare>(frame);
|
||||
case 11: return Decode<AMQPExchangeDeclareOk>(frame);
|
||||
case 20: return Decode<AMQPExchangeDelete>(frame);
|
||||
case 21: return Decode<AMQPExchangeDeleteOk>(frame);
|
||||
}
|
||||
break;
|
||||
case AMQP_CLASS_QUEUE:
|
||||
switch (methodId) {
|
||||
case 10: return Decode<AMQPQueueDeclare>(frame);
|
||||
case 11: return Decode<AMQPQueueDeclareOk>(frame);
|
||||
case 20: return Decode<AMQPQueueBind>(frame);
|
||||
case 21: return Decode<AMQPQueueBindOk>(frame);
|
||||
case 30: return Decode<AMQPQueuePurge>(frame);
|
||||
case 31: return Decode<AMQPQueuePurgeOk>(frame);
|
||||
case 40: return Decode<AMQPQueueDelete>(frame);
|
||||
case 41: return Decode<AMQPQueueDeleteOk>(frame);
|
||||
case 50: return Decode<AMQPQueueUnbind>(frame);
|
||||
case 51: return Decode<AMQPQueueUnbindOk>(frame);
|
||||
}
|
||||
break;
|
||||
case AMQP_CLASS_BASIC:
|
||||
switch (methodId) {
|
||||
case 10: return Decode<AMQPBasicQos>(frame);
|
||||
case 11: return Decode<AMQPBasicQosOk>(frame);
|
||||
case 20: return Decode<AMQPBasicConsume>(frame);
|
||||
case 21: return Decode<AMQPBasicConsumeOk>(frame);
|
||||
case 30: return Decode<AMQPBasicCancel>(frame);
|
||||
case 31: return Decode<AMQPBasicCancelOk>(frame);
|
||||
case 40: return Decode<AMQPBasicPublish>(frame);
|
||||
case 50: return Decode<AMQPBasicReturn>(frame);
|
||||
case 60: return Decode<AMQPBasicDeliver>(frame);
|
||||
case 70: return Decode<AMQPBasicGet>(frame);
|
||||
case 71: return Decode<AMQPBasicGetOk>(frame);
|
||||
case 72: return Decode<AMQPBasicGetEmpty>(frame);
|
||||
case 80: return Decode<AMQPBasicAck>(frame);
|
||||
case 90: return Decode<AMQPBasicReject>(frame);
|
||||
case 100: return Decode<AMQPBasicRecoverAsync>(frame);
|
||||
case 110: return Decode<AMQPBasicRecover>(frame);
|
||||
case 111: return Decode<AMQPBasicRecoverOk>(frame);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
idMQContentHeader* idMQChannel::DecodeContentHeader(idMQFrame& frame) {
|
||||
const int classId = frame.ReadUInt16();
|
||||
frame.ReadUInt16(); // weight, reserved and always zero
|
||||
const std::uint64_t bodyLength = frame.ReadUInt64();
|
||||
if (classId != AMQP_CLASS_BASIC) return nullptr;
|
||||
AMQPBasicProperties* header = new AMQPBasicProperties();
|
||||
header->bodyLength = bodyLength;
|
||||
header->Populate(frame);
|
||||
return header;
|
||||
}
|
||||
|
||||
idMQConsumer* idMQChannel::LookupConsumer(const idStr& tag) {
|
||||
for (int index = 0; index < consumers.Num(); ++index) {
|
||||
if (consumers[index] != nullptr
|
||||
&& _stricmp(consumers[index]->consumerTag.c_str(),
|
||||
tag.c_str()) == 0) {
|
||||
return consumers[index];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool idMQChannel::RemoveConsumer(const idStr& tag) {
|
||||
for (int index = 0; index < consumers.Num(); ++index) {
|
||||
if (consumers[index] != nullptr
|
||||
&& _stricmp(consumers[index]->consumerTag.c_str(),
|
||||
tag.c_str()) == 0) {
|
||||
return consumers.RemoveIndex(index);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
MQErrors_t idMQChannel::HandleCommand(idMQCommand& command) {
|
||||
if (command.method == nullptr) return MQERROR_UNEXPECTED_METHOD;
|
||||
const int classId = command.method->GetClassId();
|
||||
const int methodId = command.method->GetMethodId();
|
||||
if (classId == AMQP_CLASS_CONNECTION) {
|
||||
switch (methodId) {
|
||||
case 10: deferredConnectionStart.SetValue(
|
||||
*static_cast<AMQPConnectionStart*>(command.method)); break;
|
||||
case 30: deferredConnectionTune.SetValue(
|
||||
*static_cast<AMQPConnectionTune*>(command.method)); break;
|
||||
case 41: deferredConnectionOpenOk.SetValue(
|
||||
*static_cast<AMQPConnectionOpenOk*>(command.method)); break;
|
||||
case 60: {
|
||||
AMQPConnectionCloseOk response;
|
||||
SendMethod(session, response);
|
||||
return MQERROR_SERVER_CLOSED;
|
||||
}
|
||||
case 61: break;
|
||||
default: return MQERROR_UNEXPECTED_METHOD;
|
||||
}
|
||||
return MQERROR_NONE;
|
||||
}
|
||||
if (classId == AMQP_CLASS_CHANNEL) {
|
||||
switch (methodId) {
|
||||
case 11: deferredChannelOpenOk.SetValue(
|
||||
*static_cast<AMQPChannelOpenOk*>(command.method)); break;
|
||||
case 20: {
|
||||
AMQPChannelFlow& flow = *static_cast<AMQPChannelFlow*>(command.method);
|
||||
deferredChannelFlow.SetValue(flow);
|
||||
AMQPChannelFlowOk response;
|
||||
response.active = flow.active;
|
||||
SendMethod(session, response);
|
||||
break;
|
||||
}
|
||||
case 21: deferredChannelFlowOk.SetValue(
|
||||
*static_cast<AMQPChannelFlowOk*>(command.method)); break;
|
||||
case 40: {
|
||||
AMQPChannelCloseOk response;
|
||||
SendMethod(session, response);
|
||||
return MQERROR_SERVER_CLOSED;
|
||||
}
|
||||
case 41: break;
|
||||
default: return MQERROR_UNEXPECTED_METHOD;
|
||||
}
|
||||
return MQERROR_NONE;
|
||||
}
|
||||
if (classId == AMQP_CLASS_EXCHANGE) {
|
||||
if (methodId == 11) deferredExchangeDeclareOk.SetValue(
|
||||
*static_cast<AMQPExchangeDeclareOk*>(command.method));
|
||||
else if (methodId == 21) deferredExchangeDeleteOk.SetValue(
|
||||
*static_cast<AMQPExchangeDeleteOk*>(command.method));
|
||||
else return MQERROR_UNEXPECTED_METHOD;
|
||||
return MQERROR_NONE;
|
||||
}
|
||||
if (classId == AMQP_CLASS_QUEUE) {
|
||||
switch (methodId) {
|
||||
case 11: deferredQueueDeclareOk.SetValue(
|
||||
*static_cast<AMQPQueueDeclareOk*>(command.method)); break;
|
||||
case 21: deferredQueueBindOk.SetValue(
|
||||
*static_cast<AMQPQueueBindOk*>(command.method)); break;
|
||||
case 31: deferredQueuePurgeOk.SetValue(
|
||||
*static_cast<AMQPQueuePurgeOk*>(command.method)); break;
|
||||
case 41: deferredQueueDeleteOk.SetValue(
|
||||
*static_cast<AMQPQueueDeleteOk*>(command.method)); break;
|
||||
default: return MQERROR_UNEXPECTED_METHOD;
|
||||
}
|
||||
return MQERROR_NONE;
|
||||
}
|
||||
if (classId == AMQP_CLASS_BASIC) {
|
||||
switch (methodId) {
|
||||
case 21: {
|
||||
AMQPBasicConsumeOk& value =
|
||||
*static_cast<AMQPBasicConsumeOk*>(command.method);
|
||||
deferredBasicConsumeOk.SetValue(value);
|
||||
idMQConsumer* consumer = LookupConsumer(value.consumerTag);
|
||||
if (consumer != nullptr) consumer->ProcessBasicConsumeOk(
|
||||
value.consumerTag);
|
||||
break;
|
||||
}
|
||||
case 30: {
|
||||
AMQPBasicCancel& value =
|
||||
*static_cast<AMQPBasicCancel*>(command.method);
|
||||
idMQConsumer* consumer = LookupConsumer(value.consumerTag);
|
||||
if (consumer != nullptr) consumer->ProcessBasicCancel(
|
||||
value.consumerTag);
|
||||
RemoveConsumer(value.consumerTag);
|
||||
break;
|
||||
}
|
||||
case 31: deferredBasicCancelOk.SetValue(
|
||||
*static_cast<AMQPBasicCancelOk*>(command.method)); break;
|
||||
case 60: {
|
||||
AMQPBasicDeliver& value =
|
||||
*static_cast<AMQPBasicDeliver*>(command.method);
|
||||
idMQConsumer* consumer = LookupConsumer(value.consumerTag);
|
||||
if (consumer != nullptr) consumer->ProcessBasicDeliver(
|
||||
value.consumerTag, value.deliveryTag, value.redelivered,
|
||||
value.exchange, value.routingKey,
|
||||
static_cast<AMQPBasicProperties*>(command.contentHeader),
|
||||
command.contentBody);
|
||||
break;
|
||||
}
|
||||
case 71: {
|
||||
AMQPBasicGetOk& value =
|
||||
*static_cast<AMQPBasicGetOk*>(command.method);
|
||||
amqpBasicGetResultOk_t result;
|
||||
result.deliveryTag = value.deliveryTag;
|
||||
result.redelivered = value.redelivered;
|
||||
result.exchange = value.exchange;
|
||||
result.routingKey = value.routingKey;
|
||||
result.messageCount = value.messageCount;
|
||||
result.header = command.contentHeader == nullptr ? nullptr
|
||||
: new AMQPBasicProperties(
|
||||
*static_cast<AMQPBasicProperties*>(command.contentHeader));
|
||||
result.body = command.contentBody;
|
||||
deferredBasicGetResultOk.SetValue(result);
|
||||
break;
|
||||
}
|
||||
case 111: deferredAMQPBasicRecoverOk.SetValue(
|
||||
*static_cast<AMQPBasicRecoverOk*>(command.method)); break;
|
||||
case 50: case 72: case 80: case 90: break;
|
||||
default: return MQERROR_UNEXPECTED_METHOD;
|
||||
}
|
||||
return MQERROR_NONE;
|
||||
}
|
||||
return MQERROR_UNEXPECTED_CLASS;
|
||||
}
|
||||
|
||||
void idMQChannel::ConnectionStartOk(AMQPConnectionTune& tune,
|
||||
const idStr& mechanism, const idMQList<unsigned char>& response,
|
||||
const idStr& locale) {
|
||||
idScopedCriticalSection lock(protocolWait);
|
||||
AMQPConnectionStartOk request;
|
||||
request.mechanism = mechanism;
|
||||
request.response = response;
|
||||
request.locale = locale;
|
||||
SendMethod(session, request);
|
||||
deferredConnectionTune.GetValue(tune);
|
||||
}
|
||||
|
||||
void idMQChannel::ConnectionTuneOk(const unsigned short channelMax,
|
||||
const unsigned int frameMax, const unsigned short heartbeat) {
|
||||
AMQPConnectionTuneOk request;
|
||||
request.channelMax = channelMax;
|
||||
request.frameMax = frameMax;
|
||||
request.heartbeat = heartbeat;
|
||||
SendMethod(session, request);
|
||||
}
|
||||
|
||||
void idMQChannel::ConnectionOpen(idStr& knownHosts, const idStr& vhost,
|
||||
const idStr& capabilities, const bool insist) {
|
||||
idScopedCriticalSection lock(protocolWait);
|
||||
AMQPConnectionOpen request;
|
||||
request.virtualHost = vhost;
|
||||
request.capabilities = capabilities;
|
||||
request.insist = insist;
|
||||
SendMethod(session, request);
|
||||
AMQPConnectionOpenOk result;
|
||||
if (deferredConnectionOpenOk.GetValue(result)) knownHosts = result.knownHosts;
|
||||
}
|
||||
|
||||
void idMQChannel::ConnectionClose(const unsigned short replyCode,
|
||||
const idStr& replyText, const unsigned short classId,
|
||||
const unsigned short methodId) {
|
||||
AMQPConnectionClose request;
|
||||
request.replyCode = replyCode;
|
||||
request.replyText = replyText;
|
||||
request.cId = classId;
|
||||
request.mId = methodId;
|
||||
SendMethod(session, request);
|
||||
}
|
||||
|
||||
void idMQChannel::ChannelOpen(const idStr& outOfBand) {
|
||||
idScopedCriticalSection lock(protocolWait);
|
||||
AMQPChannelOpen request;
|
||||
request.oob = outOfBand;
|
||||
SendMethod(session, request);
|
||||
deferredChannelOpenOk.GetValue();
|
||||
}
|
||||
|
||||
void idMQChannel::ExchangeDeclareInternal(const idStr& exchange,
|
||||
const idStr& type, const bool passive, const bool durable,
|
||||
const bool autoDelete, const bool internal, const bool noWait) {
|
||||
idScopedCriticalSection lock(protocolWait);
|
||||
AMQPExchangeDeclare request;
|
||||
request.exchange = exchange;
|
||||
request.type = type;
|
||||
request.passive = passive;
|
||||
request.durable = durable;
|
||||
request.autoDelete = autoDelete;
|
||||
request.isInternal = internal;
|
||||
request.noWait = noWait;
|
||||
SendMethod(session, request);
|
||||
if (!noWait) deferredExchangeDeclareOk.GetValue();
|
||||
}
|
||||
|
||||
void idMQChannel::ExchangeDeclare(const idStr& exchange, const idStr& type,
|
||||
const bool durable) {
|
||||
ExchangeDeclareInternal(exchange, type, false, durable, false, false,
|
||||
false);
|
||||
}
|
||||
|
||||
void idMQChannel::ExchangeDeclare(const idStr& exchange, const idStr& type,
|
||||
const bool durable, const bool autoDelete) {
|
||||
ExchangeDeclareInternal(exchange, type, false, durable, autoDelete,
|
||||
false, false);
|
||||
}
|
||||
|
||||
void idMQChannel::BasicPublishInternal(const idStr& exchange,
|
||||
const idStr& routingKey, const bool mandatory, const bool immediate,
|
||||
const idMQList<unsigned char>& body) {
|
||||
AMQPBasicPublish method;
|
||||
method.exchange = exchange;
|
||||
method.routingKey = routingKey;
|
||||
method.mandatory = mandatory;
|
||||
method.immediate = immediate;
|
||||
AMQPBasicProperties properties;
|
||||
properties.bodyLength = body.Num();
|
||||
idMQCommand command;
|
||||
command.method = &method;
|
||||
command.contentHeader = &properties;
|
||||
command.contentBody = body;
|
||||
session->SendCommand(command);
|
||||
}
|
||||
|
||||
void idMQChannel::BasicPublish(const idStr& exchange,
|
||||
const idStr& routingKey, const idMQList<unsigned char>& body) {
|
||||
BasicPublishInternal(exchange, routingKey, false, false, body);
|
||||
}
|
||||
|
||||
void idMQChannel::BasicPublish(const idStr& exchange,
|
||||
const idStr& routingKey, const bool mandatory, const bool immediate,
|
||||
const void* data, const unsigned int size) {
|
||||
idMQList<unsigned char> body(16);
|
||||
const unsigned char* bytes = static_cast<const unsigned char*>(data);
|
||||
for (unsigned int index = 0; index < size; ++index) body.Append(bytes[index]);
|
||||
BasicPublishInternal(exchange, routingKey, mandatory, immediate, body);
|
||||
}
|
||||
|
||||
void idMQChannel::Close() {
|
||||
if (session != nullptr) session->Close();
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
#include "mqmessaging.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
idMQSession::idMQSession(const int number, idMQConnection* owner)
|
||||
: connection(owner), cmdBuilder(), protocol(nullptr), currentCommand(),
|
||||
channelNumber(number), isOpen(false) {
|
||||
}
|
||||
|
||||
idMQSession::~idMQSession() {
|
||||
currentCommand.Reset();
|
||||
delete protocol;
|
||||
}
|
||||
|
||||
void idMQSession::Init(const bool connectionChannel) {
|
||||
if (protocol == nullptr) protocol = new idMQChannel(this);
|
||||
protocol->Init();
|
||||
isOpen = true;
|
||||
if (!connectionChannel) protocol->ChannelOpen();
|
||||
}
|
||||
|
||||
void idMQSession::Close() {
|
||||
Close("client shutdown", MQERROR_NONE, true);
|
||||
}
|
||||
|
||||
void idMQSession::Close(const char*, const MQErrors_t, const bool notifyServer) {
|
||||
if (!isOpen) return;
|
||||
if (notifyServer && channelNumber != 0 && protocol != nullptr) {
|
||||
AMQPChannelClose request;
|
||||
request.replyCode = 200;
|
||||
request.replyText = "Goodbye";
|
||||
idMQCommand command;
|
||||
command.method = &request;
|
||||
SendCommand(command);
|
||||
}
|
||||
isOpen = false;
|
||||
}
|
||||
|
||||
void idMQSession::HandleFrame(idMQFrame& frame) {
|
||||
const MQErrors_t error = cmdBuilder.HandleFrame(currentCommand, frame);
|
||||
if (error != MQERROR_NONE) {
|
||||
Close("invalid frame", error, false);
|
||||
currentCommand.Reset();
|
||||
return;
|
||||
}
|
||||
if (currentCommand.complete) {
|
||||
const MQErrors_t result = protocol == nullptr
|
||||
? MQERROR_UNKNOWN : protocol->HandleCommand(currentCommand);
|
||||
if (result != MQERROR_NONE && result != MQERROR_SERVER_CLOSED) {
|
||||
Close("command handling failed", result, false);
|
||||
}
|
||||
currentCommand.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void idMQSession::SendCommand(idMQCommand& command) {
|
||||
if (connection == nullptr || command.method == nullptr) return;
|
||||
idMQFrame methodFrame(AMQP_FRAME_METHOD,
|
||||
static_cast<unsigned short>(channelNumber));
|
||||
methodFrame.WriteUInt16(command.method->GetClassId());
|
||||
methodFrame.WriteUInt16(command.method->GetMethodId());
|
||||
command.method->ToFrame(methodFrame);
|
||||
connection->SendFrame(methodFrame);
|
||||
|
||||
if (!command.method->HasContent() || command.contentHeader == nullptr) return;
|
||||
command.contentHeader->bodyLength = command.contentBody.Num();
|
||||
idMQFrame headerFrame(AMQP_FRAME_HEADER,
|
||||
static_cast<unsigned short>(channelNumber));
|
||||
headerFrame.WriteUInt16(command.contentHeader->GetProtocolId());
|
||||
headerFrame.WriteUInt16(0);
|
||||
headerFrame.WriteUInt64(command.contentHeader->bodyLength);
|
||||
command.contentHeader->ToFrame(headerFrame);
|
||||
connection->SendFrame(headerFrame);
|
||||
|
||||
const unsigned int maximum = connection->frameMaxSize > 8
|
||||
? connection->frameMaxSize - 8 : 131064;
|
||||
int offset = 0;
|
||||
while (offset < command.contentBody.Num()) {
|
||||
const int amount = std::min(command.contentBody.Num() - offset,
|
||||
static_cast<int>(maximum));
|
||||
idMQFrame bodyFrame(AMQP_FRAME_BODY,
|
||||
static_cast<unsigned short>(channelNumber));
|
||||
bodyFrame.WriteData(command.contentBody.Ptr() + offset, amount);
|
||||
connection->SendFrame(bodyFrame);
|
||||
offset += amount;
|
||||
}
|
||||
}
|
||||
|
||||
idMQSessionManager::idMQSessionManager(idMQConnection* owner)
|
||||
: initialized(false), connection(owner), maxSessions(0), sessionList() {
|
||||
}
|
||||
|
||||
idMQSessionManager::~idMQSessionManager() { Shutdown(); }
|
||||
|
||||
void idMQSessionManager::Init(const int maximumSessions) {
|
||||
Shutdown();
|
||||
maxSessions = maximumSessions <= 0 ? 65535 : maximumSessions;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
void idMQSessionManager::Shutdown() {
|
||||
for (int index = 0; index < sessionList.Num(); ++index) {
|
||||
delete sessionList[index];
|
||||
}
|
||||
sessionList.Clear();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
idMQSession* idMQSessionManager::CreateSession() {
|
||||
if (!initialized || sessionList.Num() >= maxSessions) return nullptr;
|
||||
int number = 1;
|
||||
while (Lookup(number) != nullptr) ++number;
|
||||
idMQSession* session = new idMQSession(number, connection);
|
||||
sessionList.Append(session);
|
||||
session->Init(false);
|
||||
return session;
|
||||
}
|
||||
|
||||
idMQSession* idMQSessionManager::Lookup(const int number) {
|
||||
for (int index = 0; index < sessionList.Num(); ++index) {
|
||||
if (sessionList[index] != nullptr
|
||||
&& sessionList[index]->channelNumber == number) {
|
||||
return sessionList[index];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#include "mqcommon.h"
|
||||
|
||||
#ifdef nullptr
|
||||
#undef nullptr
|
||||
#endif
|
||||
|
||||
idMQClientThread::idMQClientThread()
|
||||
: handle(0), signalQuit(false), terminated(true), connection(),
|
||||
retryTime(0) {
|
||||
}
|
||||
|
||||
idMQClientThread::~idMQClientThread() { StopThread(true); }
|
||||
|
||||
void idMQClientThread::StopThread(const bool waitForStop) {
|
||||
signalQuit = true;
|
||||
connection.Close();
|
||||
if (waitForStop && handle != 0) {
|
||||
Sys_WaitForThread(static_cast<uintptr_t>(handle));
|
||||
Sys_DestroyThread(static_cast<uintptr_t>(handle));
|
||||
handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void idMQClientThread::Connect(idMQClientThread* thread) {
|
||||
if (thread == nullptr) return;
|
||||
amqpEndpoint_t endpoint;
|
||||
thread->connection.Init(endpoint);
|
||||
int delay = 1000;
|
||||
for (int attempt = 0; attempt < 4 && !thread->signalQuit; ++attempt) {
|
||||
if (thread->connection.Connect()) {
|
||||
thread->retryTime = 0;
|
||||
return;
|
||||
}
|
||||
thread->retryTime = static_cast<int>(GetTickCount()) + delay;
|
||||
const int end = thread->retryTime;
|
||||
while (!thread->signalQuit
|
||||
&& static_cast<int>(GetTickCount()) < end) Sleep(25);
|
||||
delay += 1000;
|
||||
}
|
||||
thread->signalQuit = true;
|
||||
}
|
||||
|
||||
unsigned int idMQClientThread::Thread(void* value) {
|
||||
idMQClientThread* thread = static_cast<idMQClientThread*>(value);
|
||||
Connect(thread);
|
||||
if (!thread->signalQuit) thread->PreRun();
|
||||
while (!thread->signalQuit) {
|
||||
thread->ThreadSlice();
|
||||
if (thread->connection.connectionTerminated && !thread->signalQuit) {
|
||||
Connect(thread);
|
||||
if (!thread->signalQuit) thread->PreRun();
|
||||
}
|
||||
}
|
||||
thread->connection.Close();
|
||||
thread->OnThreadTerminate();
|
||||
thread->terminated = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void idMQClientThread::StartThread(const char* threadName) {
|
||||
if (handle != 0) return;
|
||||
signalQuit = false;
|
||||
terminated = false;
|
||||
handle = static_cast<unsigned int>(Sys_CreateThread(Thread, this,
|
||||
THREAD_LOWEST, threadName == nullptr ? "AMQP Client" : threadName,
|
||||
CORE_ANY, 0x20000, false));
|
||||
}
|
||||
@@ -0,0 +1,584 @@
|
||||
#include "reports.pb.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
namespace {
|
||||
|
||||
void WriteVarint(std::string& output, std::uint64_t value) {
|
||||
while (value >= 0x80) {
|
||||
output.push_back(static_cast<char>((value & 0x7f) | 0x80));
|
||||
value >>= 7;
|
||||
}
|
||||
output.push_back(static_cast<char>(value));
|
||||
}
|
||||
|
||||
void WriteTag(std::string& output, const int number, const int wireType) {
|
||||
WriteVarint(output, (static_cast<std::uint64_t>(number) << 3) | wireType);
|
||||
}
|
||||
|
||||
void WriteLengthDelimited(std::string& output, const int number,
|
||||
const std::string& value) {
|
||||
WriteTag(output, number, 2);
|
||||
WriteVarint(output, value.size());
|
||||
output.append(value);
|
||||
}
|
||||
|
||||
bool ReadVarint(const unsigned char*& cursor, const unsigned char* end,
|
||||
std::uint64_t& value) {
|
||||
value = 0;
|
||||
for (int shift = 0; shift < 70 && cursor < end; shift += 7) {
|
||||
const unsigned char byteValue = *cursor++;
|
||||
if (shift < 64) value |= static_cast<std::uint64_t>(byteValue & 0x7f)
|
||||
<< shift;
|
||||
if ((byteValue & 0x80) == 0) return shift < 64 || byteValue <= 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReadLength(const unsigned char*& cursor, const unsigned char* end,
|
||||
const unsigned char*& fieldEnd) {
|
||||
std::uint64_t size = 0;
|
||||
if (!ReadVarint(cursor, end, size)
|
||||
|| size > static_cast<std::uint64_t>(end - cursor)) return false;
|
||||
fieldEnd = cursor + static_cast<std::size_t>(size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkipField(const int wireType, const unsigned char*& cursor,
|
||||
const unsigned char* end) {
|
||||
std::uint64_t ignored = 0;
|
||||
const unsigned char* fieldEnd = 0;
|
||||
switch (wireType) {
|
||||
case 0: return ReadVarint(cursor, end, ignored);
|
||||
case 1:
|
||||
if (end - cursor < 8) return false;
|
||||
cursor += 8;
|
||||
return true;
|
||||
case 2:
|
||||
if (!ReadLength(cursor, end, fieldEnd)) return false;
|
||||
cursor = fieldEnd;
|
||||
return true;
|
||||
case 5:
|
||||
if (end - cursor < 4) return false;
|
||||
cursor += 4;
|
||||
return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<google::protobuf::MessageLite> CloneMessage(
|
||||
const std::shared_ptr<google::protobuf::MessageLite>& source) {
|
||||
if (!source) return std::shared_ptr<google::protobuf::MessageLite>();
|
||||
std::shared_ptr<google::protobuf::MessageLite> result(source->New());
|
||||
std::string data;
|
||||
if (!source->SerializePartialToString(&data)
|
||||
|| !result->ParseFromString(data)) {
|
||||
return std::shared_ptr<google::protobuf::MessageLite>();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
|
||||
bool MessageLite::SerializeToArray(void* data, const int size) const {
|
||||
return IsInitialized() && SerializePartialToArray(data, size);
|
||||
}
|
||||
|
||||
bool MessageLite::SerializePartialToString(std::string* output) const {
|
||||
if (output == 0) return false;
|
||||
const int size = ByteSize();
|
||||
output->resize(size);
|
||||
return size == 0 || SerializePartialToArray(&(*output)[0], size);
|
||||
}
|
||||
|
||||
bool MessageLite::SerializeToString(std::string* output) const {
|
||||
return IsInitialized() && SerializePartialToString(output);
|
||||
}
|
||||
|
||||
std::string MessageLite::SerializeAsString() const {
|
||||
std::string output;
|
||||
SerializeToString(&output);
|
||||
return output;
|
||||
}
|
||||
|
||||
bool MessageLite::ParseFromString(const std::string& input) {
|
||||
return ParseFromArray(input.data(), static_cast<int>(input.size()));
|
||||
}
|
||||
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
|
||||
namespace idreports {
|
||||
|
||||
bool LogEvent_Severity_IsValid(const int value) {
|
||||
return value == 80 || value == 70 || value == 60 || value == 50
|
||||
|| value == 40 || value == 30 || value == 20 || value == 10
|
||||
|| value == 0;
|
||||
}
|
||||
|
||||
const char* LogEvent_Severity_Name(const LogEvent_Severity value) {
|
||||
switch (value) {
|
||||
case LogEvent_Severity_SEV_EMERGENCY: return "SEV_EMERGENCY";
|
||||
case LogEvent_Severity_SEV_ALERT: return "SEV_ALERT";
|
||||
case LogEvent_Severity_SEV_CRITICAL: return "SEV_CRITICAL";
|
||||
case LogEvent_Severity_SEV_ERROR: return "SEV_ERROR";
|
||||
case LogEvent_Severity_SEV_WARNING: return "SEV_WARNING";
|
||||
case LogEvent_Severity_SEV_NOTICE: return "SEV_NOTICE";
|
||||
case LogEvent_Severity_SEV_INFO: return "SEV_INFO";
|
||||
case LogEvent_Severity_SEV_DEBUG: return "SEV_DEBUG";
|
||||
case LogEvent_Severity_SEV_TRASH: return "SEV_TRASH";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
bool TargetPlatform_IsValid(const int value) {
|
||||
return value >= TARGET_WIN32 && value <= TARGET_LINUX;
|
||||
}
|
||||
|
||||
const char* TargetPlatform_Name(const TargetPlatform value) {
|
||||
static const char* const names[] = {
|
||||
"TARGET_WIN32", "TARGET_X64", "TARGET_XBOX360", "TARGET_PS3",
|
||||
"TARGET_DURANGO", "TARGET_WIIU", "TARGET_IPHONE", "TARGET_OSX",
|
||||
"TARGET_LINUX"
|
||||
};
|
||||
return TargetPlatform_IsValid(value) ? names[value] : "UNKNOWN";
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
FieldValue::FieldValue()
|
||||
: kind(FIELD_UNKNOWN), integer(0), real(0.0f), text(), message(),
|
||||
strings(), integers(), messages() {
|
||||
}
|
||||
|
||||
ReportMessage::ReportMessage() : fields_() {
|
||||
}
|
||||
|
||||
ReportMessage::ReportMessage(const ReportMessage& other) : fields_() {
|
||||
MergeFrom(other);
|
||||
}
|
||||
|
||||
ReportMessage& ReportMessage::operator=(const ReportMessage& other) {
|
||||
if (this != &other) CopyFrom(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ReportMessage::~ReportMessage() {
|
||||
}
|
||||
|
||||
void ReportMessage::Clear() {
|
||||
fields_.clear();
|
||||
}
|
||||
|
||||
bool ReportMessage::HasField(const int number) const {
|
||||
return fields_.find(number) != fields_.end();
|
||||
}
|
||||
|
||||
void ReportMessage::ClearField(const int number) {
|
||||
fields_.erase(number);
|
||||
}
|
||||
|
||||
std::uint64_t ReportMessage::GetInteger(const int number) const {
|
||||
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
|
||||
return found == fields_.end() ? 0 : found->second.integer;
|
||||
}
|
||||
|
||||
void ReportMessage::SetInteger(const int number, const FieldKind kind,
|
||||
const std::uint64_t value) {
|
||||
FieldValue& field = fields_[number];
|
||||
field.kind = kind;
|
||||
field.integer = value;
|
||||
}
|
||||
|
||||
float ReportMessage::GetFloat(const int number) const {
|
||||
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
|
||||
return found == fields_.end() ? 0.0f : found->second.real;
|
||||
}
|
||||
|
||||
void ReportMessage::SetFloat(const int number, const float value) {
|
||||
FieldValue& field = fields_[number];
|
||||
field.kind = FIELD_FLOAT;
|
||||
field.real = value;
|
||||
}
|
||||
|
||||
const std::string& ReportMessage::GetString(const int number) const {
|
||||
static const std::string empty;
|
||||
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
|
||||
return found == fields_.end() ? empty : found->second.text;
|
||||
}
|
||||
|
||||
std::string* ReportMessage::MutableString(const int number) {
|
||||
FieldValue& field = fields_[number];
|
||||
field.kind = FIELD_STRING;
|
||||
return &field.text;
|
||||
}
|
||||
|
||||
std::string* ReportMessage::ReleaseString(const int number) {
|
||||
const std::map<int, FieldValue>::iterator found = fields_.find(number);
|
||||
if (found == fields_.end()) return 0;
|
||||
std::string* result = new std::string(found->second.text);
|
||||
fields_.erase(found);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ReportMessage::SetString(const int number, const char* value,
|
||||
const std::size_t size) {
|
||||
FieldValue& field = fields_[number];
|
||||
field.kind = FIELD_STRING;
|
||||
field.text.assign(value == 0 ? "" : value, value == 0 ? 0 : size);
|
||||
}
|
||||
|
||||
int ReportMessage::RepeatedIntegerSize(const int number) const {
|
||||
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
|
||||
return found == fields_.end() ? 0
|
||||
: static_cast<int>(found->second.integers.size());
|
||||
}
|
||||
|
||||
std::uint64_t ReportMessage::GetRepeatedInteger(const int number,
|
||||
const int index) const {
|
||||
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
|
||||
return found == fields_.end() || index < 0
|
||||
|| index >= static_cast<int>(found->second.integers.size())
|
||||
? 0 : found->second.integers[index];
|
||||
}
|
||||
|
||||
void ReportMessage::SetRepeatedInteger(const int number, const int index,
|
||||
const FieldKind kind, const std::uint64_t value) {
|
||||
FieldValue& field = fields_[number];
|
||||
field.kind = kind;
|
||||
if (index >= 0 && index < static_cast<int>(field.integers.size())) {
|
||||
field.integers[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void ReportMessage::AddRepeatedInteger(const int number, const FieldKind kind,
|
||||
const std::uint64_t value) {
|
||||
FieldValue& field = fields_[number];
|
||||
field.kind = kind;
|
||||
field.integers.push_back(value);
|
||||
}
|
||||
|
||||
int ReportMessage::RepeatedStringSize(const int number) const {
|
||||
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
|
||||
return found == fields_.end() ? 0
|
||||
: static_cast<int>(found->second.strings.size());
|
||||
}
|
||||
|
||||
const std::string& ReportMessage::GetRepeatedString(const int number,
|
||||
const int index) const {
|
||||
static const std::string empty;
|
||||
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
|
||||
return found == fields_.end() || index < 0
|
||||
|| index >= static_cast<int>(found->second.strings.size())
|
||||
? empty : found->second.strings[index];
|
||||
}
|
||||
|
||||
std::string* ReportMessage::MutableRepeatedString(const int number,
|
||||
const int index) {
|
||||
const std::map<int, FieldValue>::iterator found = fields_.find(number);
|
||||
return found == fields_.end() || index < 0
|
||||
|| index >= static_cast<int>(found->second.strings.size())
|
||||
? 0 : &found->second.strings[index];
|
||||
}
|
||||
|
||||
std::string* ReportMessage::AddRepeatedString(const int number) {
|
||||
FieldValue& field = fields_[number];
|
||||
field.kind = FIELD_REPEATED_STRING;
|
||||
field.strings.push_back(std::string());
|
||||
return &field.strings.back();
|
||||
}
|
||||
|
||||
void ReportMessage::SetRepeatedString(const int number, const int index,
|
||||
const char* value, const std::size_t size) {
|
||||
std::string* item = MutableRepeatedString(number, index);
|
||||
if (item != 0) item->assign(value == 0 ? "" : value,
|
||||
value == 0 ? 0 : size);
|
||||
}
|
||||
|
||||
int ReportMessage::RepeatedMessageSize(const int number) const {
|
||||
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
|
||||
return found == fields_.end() ? 0
|
||||
: static_cast<int>(found->second.messages.size());
|
||||
}
|
||||
|
||||
const google::protobuf::MessageLite* ReportMessage::GetRepeatedMessage(
|
||||
const int number, const int index) const {
|
||||
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
|
||||
return found == fields_.end() || index < 0
|
||||
|| index >= static_cast<int>(found->second.messages.size())
|
||||
? 0 : found->second.messages[index].get();
|
||||
}
|
||||
|
||||
google::protobuf::MessageLite* ReportMessage::MutableRepeatedMessage(
|
||||
const int number, const int index) {
|
||||
const std::map<int, FieldValue>::iterator found = fields_.find(number);
|
||||
return found == fields_.end() || index < 0
|
||||
|| index >= static_cast<int>(found->second.messages.size())
|
||||
? 0 : found->second.messages[index].get();
|
||||
}
|
||||
|
||||
google::protobuf::MessageLite* ReportMessage::AddRepeatedMessage(
|
||||
const int number) {
|
||||
std::shared_ptr<google::protobuf::MessageLite> value(
|
||||
NewMessageForField(number));
|
||||
if (!value) return 0;
|
||||
FieldValue& field = fields_[number];
|
||||
field.kind = FIELD_REPEATED_MESSAGE;
|
||||
field.messages.push_back(value);
|
||||
return value.get();
|
||||
}
|
||||
|
||||
const google::protobuf::MessageLite* ReportMessage::GetMessage(
|
||||
const int number) const {
|
||||
const std::map<int, FieldValue>::const_iterator found = fields_.find(number);
|
||||
return found == fields_.end() ? 0 : found->second.message.get();
|
||||
}
|
||||
|
||||
google::protobuf::MessageLite* ReportMessage::MutableMessage(const int number) {
|
||||
FieldValue& field = fields_[number];
|
||||
field.kind = FIELD_MESSAGE;
|
||||
if (!field.message) field.message.reset(NewMessageForField(number));
|
||||
return field.message.get();
|
||||
}
|
||||
|
||||
google::protobuf::MessageLite* ReportMessage::ReleaseMessage(const int number) {
|
||||
const std::map<int, FieldValue>::iterator found = fields_.find(number);
|
||||
if (found == fields_.end() || !found->second.message) return 0;
|
||||
google::protobuf::MessageLite* result = found->second.message->New();
|
||||
std::string data;
|
||||
found->second.message->SerializePartialToString(&data);
|
||||
result->ParseFromString(data);
|
||||
fields_.erase(found);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ReportMessage::SetAllocatedMessage(const int number,
|
||||
google::protobuf::MessageLite* value) {
|
||||
if (value == 0) {
|
||||
ClearField(number);
|
||||
return;
|
||||
}
|
||||
FieldValue& field = fields_[number];
|
||||
field.kind = FIELD_MESSAGE;
|
||||
field.message.reset(value);
|
||||
}
|
||||
|
||||
google::protobuf::MessageLite* ReportMessage::NewMessageForField(int) const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ReportMessage::IsInitialized() const {
|
||||
if (!HasRequiredFields()) return false;
|
||||
for (std::map<int, FieldValue>::const_iterator field = fields_.begin();
|
||||
field != fields_.end(); ++field) {
|
||||
if (field->second.message && !field->second.message->IsInitialized()) {
|
||||
return false;
|
||||
}
|
||||
for (std::size_t index = 0; index < field->second.messages.size();
|
||||
++index) {
|
||||
if (field->second.messages[index]
|
||||
&& !field->second.messages[index]->IsInitialized()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReportMessage::Serialize(std::string& output) const {
|
||||
output.clear();
|
||||
for (std::map<int, FieldValue>::const_iterator iterator = fields_.begin();
|
||||
iterator != fields_.end(); ++iterator) {
|
||||
const int number = iterator->first;
|
||||
const FieldValue& field = iterator->second;
|
||||
switch (field.kind) {
|
||||
case FIELD_STRING:
|
||||
WriteLengthDelimited(output, number, field.text);
|
||||
break;
|
||||
case FIELD_UINT32:
|
||||
case FIELD_UINT64:
|
||||
case FIELD_INT32:
|
||||
case FIELD_INT64:
|
||||
case FIELD_ENUM:
|
||||
case FIELD_BOOL:
|
||||
WriteTag(output, number, 0);
|
||||
WriteVarint(output, field.integer);
|
||||
break;
|
||||
case FIELD_FLOAT: {
|
||||
WriteTag(output, number, 5);
|
||||
std::uint32_t bits = 0;
|
||||
std::memcpy(&bits, &field.real, sizeof(bits));
|
||||
output.push_back(static_cast<char>(bits));
|
||||
output.push_back(static_cast<char>(bits >> 8));
|
||||
output.push_back(static_cast<char>(bits >> 16));
|
||||
output.push_back(static_cast<char>(bits >> 24));
|
||||
break;
|
||||
}
|
||||
case FIELD_MESSAGE:
|
||||
if (field.message) {
|
||||
std::string nested;
|
||||
field.message->SerializePartialToString(&nested);
|
||||
WriteLengthDelimited(output, number, nested);
|
||||
}
|
||||
break;
|
||||
case FIELD_REPEATED_STRING:
|
||||
for (std::size_t index = 0; index < field.strings.size(); ++index) {
|
||||
WriteLengthDelimited(output, number, field.strings[index]);
|
||||
}
|
||||
break;
|
||||
case FIELD_REPEATED_UINT32:
|
||||
case FIELD_REPEATED_UINT64:
|
||||
for (std::size_t index = 0; index < field.integers.size(); ++index) {
|
||||
WriteTag(output, number, 0);
|
||||
WriteVarint(output, field.integers[index]);
|
||||
}
|
||||
break;
|
||||
case FIELD_REPEATED_MESSAGE:
|
||||
for (std::size_t index = 0; index < field.messages.size(); ++index) {
|
||||
if (field.messages[index]) {
|
||||
std::string nested;
|
||||
field.messages[index]->SerializePartialToString(&nested);
|
||||
WriteLengthDelimited(output, number, nested);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int ReportMessage::ByteSize() const {
|
||||
std::string output;
|
||||
Serialize(output);
|
||||
return static_cast<int>(output.size());
|
||||
}
|
||||
|
||||
bool ReportMessage::SerializePartialToArray(void* data, const int size) const {
|
||||
if (size < 0 || (data == 0 && size != 0)) return false;
|
||||
std::string output;
|
||||
Serialize(output);
|
||||
if (size < static_cast<int>(output.size())) return false;
|
||||
if (!output.empty()) std::memcpy(data, output.data(), output.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReportMessage::ParseFromArray(const void* data, const int size) {
|
||||
if (size < 0 || (data == 0 && size != 0)) return false;
|
||||
Clear();
|
||||
const unsigned char* cursor = static_cast<const unsigned char*>(data);
|
||||
const unsigned char* const end = cursor + size;
|
||||
while (cursor < end) {
|
||||
std::uint64_t tag = 0;
|
||||
if (!ReadVarint(cursor, end, tag) || tag == 0) return false;
|
||||
const int number = static_cast<int>(tag >> 3);
|
||||
const int wireType = static_cast<int>(tag & 7);
|
||||
const FieldKind kind = FieldKindForNumber(number);
|
||||
if (kind == FIELD_UNKNOWN) {
|
||||
if (!SkipField(wireType, cursor, end)) return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::uint64_t integer = 0;
|
||||
const unsigned char* fieldEnd = 0;
|
||||
if (kind == FIELD_STRING || kind == FIELD_REPEATED_STRING
|
||||
|| kind == FIELD_MESSAGE || kind == FIELD_REPEATED_MESSAGE) {
|
||||
if (wireType != 2 || !ReadLength(cursor, end, fieldEnd)) return false;
|
||||
if (kind == FIELD_STRING) {
|
||||
SetString(number, reinterpret_cast<const char*>(cursor),
|
||||
static_cast<std::size_t>(fieldEnd - cursor));
|
||||
} else if (kind == FIELD_REPEATED_STRING) {
|
||||
std::string* item = AddRepeatedString(number);
|
||||
item->assign(reinterpret_cast<const char*>(cursor),
|
||||
static_cast<std::size_t>(fieldEnd - cursor));
|
||||
} else {
|
||||
google::protobuf::MessageLite* nested = kind == FIELD_MESSAGE
|
||||
? MutableMessage(number) : AddRepeatedMessage(number);
|
||||
if (nested == 0 || !nested->ParseFromArray(cursor,
|
||||
static_cast<int>(fieldEnd - cursor))) return false;
|
||||
}
|
||||
cursor = fieldEnd;
|
||||
} else if (kind == FIELD_FLOAT) {
|
||||
if (wireType != 5 || end - cursor < 4) return false;
|
||||
const std::uint32_t bits = static_cast<std::uint32_t>(cursor[0])
|
||||
| (static_cast<std::uint32_t>(cursor[1]) << 8)
|
||||
| (static_cast<std::uint32_t>(cursor[2]) << 16)
|
||||
| (static_cast<std::uint32_t>(cursor[3]) << 24);
|
||||
float value = 0.0f;
|
||||
std::memcpy(&value, &bits, sizeof(value));
|
||||
SetFloat(number, value);
|
||||
cursor += 4;
|
||||
} else if ((kind == FIELD_REPEATED_UINT32
|
||||
|| kind == FIELD_REPEATED_UINT64) && wireType == 2) {
|
||||
if (!ReadLength(cursor, end, fieldEnd)) return false;
|
||||
while (cursor < fieldEnd) {
|
||||
if (!ReadVarint(cursor, fieldEnd, integer)) return false;
|
||||
AddRepeatedInteger(number, kind, integer);
|
||||
}
|
||||
} else {
|
||||
if (wireType != 0 || !ReadVarint(cursor, end, integer)) return false;
|
||||
if (kind == FIELD_REPEATED_UINT32
|
||||
|| kind == FIELD_REPEATED_UINT64) {
|
||||
AddRepeatedInteger(number, kind, integer);
|
||||
} else {
|
||||
SetInteger(number, kind, integer);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReportMessage::CopyFrom(const google::protobuf::MessageLite& other) {
|
||||
Clear();
|
||||
MergeFrom(other);
|
||||
}
|
||||
|
||||
void ReportMessage::MergeFrom(const google::protobuf::MessageLite& other) {
|
||||
const ReportMessage* source = dynamic_cast<const ReportMessage*>(&other);
|
||||
if (source == 0 || source == this || source->GetTypeName() != GetTypeName()) {
|
||||
return;
|
||||
}
|
||||
for (std::map<int, FieldValue>::const_iterator iterator =
|
||||
source->fields_.begin(); iterator != source->fields_.end(); ++iterator) {
|
||||
const FieldValue& incoming = iterator->second;
|
||||
FieldValue& destination = fields_[iterator->first];
|
||||
if (incoming.kind == FIELD_REPEATED_STRING) {
|
||||
destination.kind = incoming.kind;
|
||||
destination.strings.insert(destination.strings.end(),
|
||||
incoming.strings.begin(), incoming.strings.end());
|
||||
} else if (incoming.kind == FIELD_REPEATED_UINT32
|
||||
|| incoming.kind == FIELD_REPEATED_UINT64) {
|
||||
destination.kind = incoming.kind;
|
||||
destination.integers.insert(destination.integers.end(),
|
||||
incoming.integers.begin(), incoming.integers.end());
|
||||
} else if (incoming.kind == FIELD_REPEATED_MESSAGE) {
|
||||
destination.kind = incoming.kind;
|
||||
for (std::size_t index = 0; index < incoming.messages.size(); ++index) {
|
||||
destination.messages.push_back(CloneMessage(incoming.messages[index]));
|
||||
}
|
||||
} else {
|
||||
destination = incoming;
|
||||
if (incoming.message) destination.message = CloneMessage(incoming.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReportMessage::Swap(ReportMessage* other) {
|
||||
if (other != 0) fields_.swap(other->fields_);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
void protobuf_AddDesc_reports_2eproto() {
|
||||
}
|
||||
|
||||
void protobuf_ShutdownFile_reports_2eproto() {
|
||||
}
|
||||
|
||||
} // namespace idreports
|
||||
|
||||
@@ -0,0 +1,646 @@
|
||||
#pragma once
|
||||
|
||||
// Reconstructed from the protobuf 2.4 generated code in the Tungsten Xbox 360
|
||||
// image. This keeps the original protobuf-lite surface and wire format while
|
||||
// avoiding a dependency on the obsolete 2.4 runtime in the Windows port.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
|
||||
class MessageLite {
|
||||
public:
|
||||
virtual ~MessageLite() {}
|
||||
virtual std::string GetTypeName() const = 0;
|
||||
virtual MessageLite* New() const = 0;
|
||||
virtual void Clear() = 0;
|
||||
virtual bool IsInitialized() const = 0;
|
||||
virtual int ByteSize() const = 0;
|
||||
virtual int GetCachedSize() const { return ByteSize(); }
|
||||
virtual bool SerializePartialToArray(void* data, int size) const = 0;
|
||||
virtual bool ParseFromArray(const void* data, int size) = 0;
|
||||
|
||||
bool SerializeToArray(void* data, int size) const;
|
||||
bool SerializePartialToString(std::string* output) const;
|
||||
bool SerializeToString(std::string* output) const;
|
||||
std::string SerializeAsString() const;
|
||||
bool ParseFromString(const std::string& input);
|
||||
};
|
||||
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
|
||||
namespace idreports {
|
||||
|
||||
enum LogEvent_Severity {
|
||||
LogEvent_Severity_SEV_EMERGENCY = 80,
|
||||
LogEvent_Severity_SEV_ALERT = 70,
|
||||
LogEvent_Severity_SEV_CRITICAL = 60,
|
||||
LogEvent_Severity_SEV_ERROR = 50,
|
||||
LogEvent_Severity_SEV_WARNING = 40,
|
||||
LogEvent_Severity_SEV_NOTICE = 30,
|
||||
LogEvent_Severity_SEV_INFO = 20,
|
||||
LogEvent_Severity_SEV_DEBUG = 10,
|
||||
LogEvent_Severity_SEV_TRASH = 0
|
||||
};
|
||||
|
||||
bool LogEvent_Severity_IsValid(int value);
|
||||
const char* LogEvent_Severity_Name(LogEvent_Severity value);
|
||||
|
||||
enum TargetPlatform {
|
||||
TARGET_WIN32 = 0,
|
||||
TARGET_X64 = 1,
|
||||
TARGET_XBOX360 = 2,
|
||||
TARGET_PS3 = 3,
|
||||
TARGET_DURANGO = 4,
|
||||
TARGET_WIIU = 5,
|
||||
TARGET_IPHONE = 6,
|
||||
TARGET_OSX = 7,
|
||||
TARGET_LINUX = 8
|
||||
};
|
||||
|
||||
bool TargetPlatform_IsValid(int value);
|
||||
const char* TargetPlatform_Name(TargetPlatform value);
|
||||
|
||||
enum HeapType {
|
||||
UNKNOWN_HEAP = 0,
|
||||
MAP_HEAP = 1,
|
||||
SYSTEM_HEAP = 2
|
||||
};
|
||||
|
||||
enum Xbox360ConsoleType {
|
||||
XBOX360_DEVELOPMENT_KIT = 0,
|
||||
XBOX360_TEST_KIT = 1,
|
||||
XBOX360_UNKNOWN_KIT = 2
|
||||
};
|
||||
|
||||
class BigUInt;
|
||||
class Attachment;
|
||||
class LogEvent;
|
||||
class MemoryInfo;
|
||||
class ExceptionInfo;
|
||||
class CallstackLine;
|
||||
class AssertReport;
|
||||
class MapWarning;
|
||||
class MapReport;
|
||||
class ViewNoteReport;
|
||||
class StringIDReferenceReport;
|
||||
class CPUInfo;
|
||||
class RegisterInfo;
|
||||
class HeapCategoryUsage;
|
||||
class HeapUsage;
|
||||
class Xbox360VersionInfo;
|
||||
class Xbox360KitInfo;
|
||||
class XenonRegisterInfo;
|
||||
class XenonMemoryInfo;
|
||||
class PPURegisterInfo;
|
||||
class SPURegisterInfo;
|
||||
class PS3ExceptionInfo;
|
||||
class PS3VMRegionStats;
|
||||
class PS3MemoryInfo;
|
||||
class CrashReport;
|
||||
class Xbox360CrashReport;
|
||||
class PS3CrashReport;
|
||||
class CrashReportResult;
|
||||
|
||||
namespace internal {
|
||||
|
||||
enum FieldKind {
|
||||
FIELD_UNKNOWN,
|
||||
FIELD_STRING,
|
||||
FIELD_UINT32,
|
||||
FIELD_UINT64,
|
||||
FIELD_INT32,
|
||||
FIELD_INT64,
|
||||
FIELD_ENUM,
|
||||
FIELD_BOOL,
|
||||
FIELD_FLOAT,
|
||||
FIELD_MESSAGE,
|
||||
FIELD_REPEATED_STRING,
|
||||
FIELD_REPEATED_UINT32,
|
||||
FIELD_REPEATED_UINT64,
|
||||
FIELD_REPEATED_MESSAGE
|
||||
};
|
||||
|
||||
struct FieldValue {
|
||||
FieldValue();
|
||||
FieldKind kind;
|
||||
std::uint64_t integer;
|
||||
float real;
|
||||
std::string text;
|
||||
std::shared_ptr<google::protobuf::MessageLite> message;
|
||||
std::vector<std::string> strings;
|
||||
std::vector<std::uint64_t> integers;
|
||||
std::vector<std::shared_ptr<google::protobuf::MessageLite> > messages;
|
||||
};
|
||||
|
||||
class ReportMessage : public google::protobuf::MessageLite {
|
||||
public:
|
||||
ReportMessage();
|
||||
ReportMessage(const ReportMessage& other);
|
||||
ReportMessage& operator=(const ReportMessage& other);
|
||||
virtual ~ReportMessage();
|
||||
|
||||
void Clear() override;
|
||||
bool IsInitialized() const override;
|
||||
int ByteSize() const override;
|
||||
bool SerializePartialToArray(void* data, int size) const override;
|
||||
bool ParseFromArray(const void* data, int size) override;
|
||||
|
||||
void CopyFrom(const google::protobuf::MessageLite& other);
|
||||
void MergeFrom(const google::protobuf::MessageLite& other);
|
||||
void Swap(ReportMessage* other);
|
||||
|
||||
protected:
|
||||
bool HasField(int number) const;
|
||||
void ClearField(int number);
|
||||
std::uint64_t GetInteger(int number) const;
|
||||
void SetInteger(int number, FieldKind kind, std::uint64_t value);
|
||||
float GetFloat(int number) const;
|
||||
void SetFloat(int number, float value);
|
||||
const std::string& GetString(int number) const;
|
||||
std::string* MutableString(int number);
|
||||
std::string* ReleaseString(int number);
|
||||
void SetString(int number, const char* value, std::size_t size);
|
||||
int RepeatedIntegerSize(int number) const;
|
||||
std::uint64_t GetRepeatedInteger(int number, int index) const;
|
||||
void SetRepeatedInteger(int number, int index, FieldKind kind,
|
||||
std::uint64_t value);
|
||||
void AddRepeatedInteger(int number, FieldKind kind, std::uint64_t value);
|
||||
int RepeatedStringSize(int number) const;
|
||||
const std::string& GetRepeatedString(int number, int index) const;
|
||||
std::string* MutableRepeatedString(int number, int index);
|
||||
std::string* AddRepeatedString(int number);
|
||||
void SetRepeatedString(int number, int index, const char* value,
|
||||
std::size_t size);
|
||||
int RepeatedMessageSize(int number) const;
|
||||
const google::protobuf::MessageLite* GetRepeatedMessage(int number,
|
||||
int index) const;
|
||||
google::protobuf::MessageLite* MutableRepeatedMessage(int number,
|
||||
int index);
|
||||
google::protobuf::MessageLite* AddRepeatedMessage(int number);
|
||||
const google::protobuf::MessageLite* GetMessage(int number) const;
|
||||
google::protobuf::MessageLite* MutableMessage(int number);
|
||||
google::protobuf::MessageLite* ReleaseMessage(int number);
|
||||
void SetAllocatedMessage(int number, google::protobuf::MessageLite* value);
|
||||
|
||||
virtual FieldKind FieldKindForNumber(int number) const = 0;
|
||||
virtual google::protobuf::MessageLite* NewMessageForField(int number) const;
|
||||
virtual bool HasRequiredFields() const = 0;
|
||||
|
||||
private:
|
||||
bool Serialize(std::string& output) const;
|
||||
std::map<int, FieldValue> fields_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
#define IDR_DECL_string(name, number, type) \
|
||||
bool has_##name() const { return HasField(number); } \
|
||||
void clear_##name() { ClearField(number); } \
|
||||
const std::string& name() const { return GetString(number); } \
|
||||
void set_##name(const std::string& value) { SetString(number, value.data(), value.size()); } \
|
||||
void set_##name(const char* value) { SetString(number, value ? value : "", value ? std::char_traits<char>::length(value) : 0); } \
|
||||
void set_##name(const char* value, std::size_t size) { SetString(number, value, size); } \
|
||||
std::string* mutable_##name() { return MutableString(number); } \
|
||||
std::string* release_##name() { return ReleaseString(number); } \
|
||||
void set_allocated_##name(std::string* value) { if (value) { set_##name(*value); delete value; } else { clear_##name(); } }
|
||||
|
||||
#define IDR_DECL_uint32(name, number, type) \
|
||||
bool has_##name() const { return HasField(number); } \
|
||||
void clear_##name() { ClearField(number); } \
|
||||
std::uint32_t name() const { return static_cast<std::uint32_t>(GetInteger(number)); } \
|
||||
void set_##name(std::uint32_t value) { SetInteger(number, internal::FIELD_UINT32, value); }
|
||||
|
||||
#define IDR_DECL_uint64(name, number, type) \
|
||||
bool has_##name() const { return HasField(number); } \
|
||||
void clear_##name() { ClearField(number); } \
|
||||
std::uint64_t name() const { return GetInteger(number); } \
|
||||
void set_##name(std::uint64_t value) { SetInteger(number, internal::FIELD_UINT64, value); }
|
||||
|
||||
#define IDR_DECL_int32(name, number, type) \
|
||||
bool has_##name() const { return HasField(number); } \
|
||||
void clear_##name() { ClearField(number); } \
|
||||
std::int32_t name() const { return static_cast<std::int32_t>(GetInteger(number)); } \
|
||||
void set_##name(std::int32_t value) { SetInteger(number, internal::FIELD_INT32, static_cast<std::uint64_t>(static_cast<std::int64_t>(value))); }
|
||||
|
||||
#define IDR_DECL_int64(name, number, type) \
|
||||
bool has_##name() const { return HasField(number); } \
|
||||
void clear_##name() { ClearField(number); } \
|
||||
std::int64_t name() const { return static_cast<std::int64_t>(GetInteger(number)); } \
|
||||
void set_##name(std::int64_t value) { SetInteger(number, internal::FIELD_INT64, static_cast<std::uint64_t>(value)); }
|
||||
|
||||
#define IDR_DECL_enum(name, number, type) \
|
||||
bool has_##name() const { return HasField(number); } \
|
||||
void clear_##name() { ClearField(number); } \
|
||||
type name() const { return static_cast<type>(GetInteger(number)); } \
|
||||
void set_##name(type value) { SetInteger(number, internal::FIELD_ENUM, static_cast<std::uint64_t>(value)); }
|
||||
|
||||
#define IDR_DECL_bool(name, number, type) \
|
||||
bool has_##name() const { return HasField(number); } \
|
||||
void clear_##name() { ClearField(number); } \
|
||||
bool name() const { return GetInteger(number) != 0; } \
|
||||
void set_##name(bool value) { SetInteger(number, internal::FIELD_BOOL, value ? 1 : 0); }
|
||||
|
||||
#define IDR_DECL_float(name, number, type) \
|
||||
bool has_##name() const { return HasField(number); } \
|
||||
void clear_##name() { ClearField(number); } \
|
||||
float name() const { return GetFloat(number); } \
|
||||
void set_##name(float value) { SetFloat(number, value); }
|
||||
|
||||
#define IDR_DECL_message(name, number, type) \
|
||||
bool has_##name() const { return HasField(number); } \
|
||||
void clear_##name() { ClearField(number); } \
|
||||
const type& name() const { const google::protobuf::MessageLite* value = GetMessage(number); return value ? *static_cast<const type*>(value) : type::default_instance(); } \
|
||||
type* mutable_##name() { return static_cast<type*>(MutableMessage(number)); } \
|
||||
type* release_##name() { return static_cast<type*>(ReleaseMessage(number)); } \
|
||||
void set_allocated_##name(type* value) { SetAllocatedMessage(number, value); }
|
||||
|
||||
#define IDR_DECL_repeated_string(name, number, type) \
|
||||
int name##_size() const { return RepeatedStringSize(number); } \
|
||||
void clear_##name() { ClearField(number); } \
|
||||
const std::string& name(int index) const { return GetRepeatedString(number, index); } \
|
||||
std::string* mutable_##name(int index) { return MutableRepeatedString(number, index); } \
|
||||
std::string* add_##name() { return AddRepeatedString(number); } \
|
||||
void add_##name(const std::string& value) { std::string* item = AddRepeatedString(number); *item = value; } \
|
||||
void add_##name(const char* value) { std::string* item = AddRepeatedString(number); *item = value ? value : ""; } \
|
||||
void set_##name(int index, const std::string& value) { SetRepeatedString(number, index, value.data(), value.size()); } \
|
||||
void set_##name(int index, const char* value) { SetRepeatedString(number, index, value ? value : "", value ? std::char_traits<char>::length(value) : 0); }
|
||||
|
||||
#define IDR_DECL_repeated_uint32(name, number, type) \
|
||||
int name##_size() const { return RepeatedIntegerSize(number); } \
|
||||
void clear_##name() { ClearField(number); } \
|
||||
std::uint32_t name(int index) const { return static_cast<std::uint32_t>(GetRepeatedInteger(number, index)); } \
|
||||
void set_##name(int index, std::uint32_t value) { SetRepeatedInteger(number, index, internal::FIELD_REPEATED_UINT32, value); } \
|
||||
void add_##name(std::uint32_t value) { AddRepeatedInteger(number, internal::FIELD_REPEATED_UINT32, value); }
|
||||
|
||||
#define IDR_DECL_repeated_uint64(name, number, type) \
|
||||
int name##_size() const { return RepeatedIntegerSize(number); } \
|
||||
void clear_##name() { ClearField(number); } \
|
||||
std::uint64_t name(int index) const { return GetRepeatedInteger(number, index); } \
|
||||
void set_##name(int index, std::uint64_t value) { SetRepeatedInteger(number, index, internal::FIELD_REPEATED_UINT64, value); } \
|
||||
void add_##name(std::uint64_t value) { AddRepeatedInteger(number, internal::FIELD_REPEATED_UINT64, value); }
|
||||
|
||||
#define IDR_DECL_repeated_message(name, number, type) \
|
||||
int name##_size() const { return RepeatedMessageSize(number); } \
|
||||
void clear_##name() { ClearField(number); } \
|
||||
const type& name(int index) const { return *static_cast<const type*>(GetRepeatedMessage(number, index)); } \
|
||||
type* mutable_##name(int index) { return static_cast<type*>(MutableRepeatedMessage(number, index)); } \
|
||||
type* add_##name() { return static_cast<type*>(AddRepeatedMessage(number)); }
|
||||
|
||||
#define IDR_KIND_string(name, number, type) case number: return internal::FIELD_STRING;
|
||||
#define IDR_KIND_uint32(name, number, type) case number: return internal::FIELD_UINT32;
|
||||
#define IDR_KIND_uint64(name, number, type) case number: return internal::FIELD_UINT64;
|
||||
#define IDR_KIND_int32(name, number, type) case number: return internal::FIELD_INT32;
|
||||
#define IDR_KIND_int64(name, number, type) case number: return internal::FIELD_INT64;
|
||||
#define IDR_KIND_enum(name, number, type) case number: return internal::FIELD_ENUM;
|
||||
#define IDR_KIND_bool(name, number, type) case number: return internal::FIELD_BOOL;
|
||||
#define IDR_KIND_float(name, number, type) case number: return internal::FIELD_FLOAT;
|
||||
#define IDR_KIND_message(name, number, type) case number: return internal::FIELD_MESSAGE;
|
||||
#define IDR_KIND_repeated_string(name, number, type) case number: return internal::FIELD_REPEATED_STRING;
|
||||
#define IDR_KIND_repeated_uint32(name, number, type) case number: return internal::FIELD_REPEATED_UINT32;
|
||||
#define IDR_KIND_repeated_uint64(name, number, type) case number: return internal::FIELD_REPEATED_UINT64;
|
||||
#define IDR_KIND_repeated_message(name, number, type) case number: return internal::FIELD_REPEATED_MESSAGE;
|
||||
|
||||
#define IDR_FACTORY_string(name, number, type)
|
||||
#define IDR_FACTORY_uint32(name, number, type)
|
||||
#define IDR_FACTORY_uint64(name, number, type)
|
||||
#define IDR_FACTORY_int32(name, number, type)
|
||||
#define IDR_FACTORY_int64(name, number, type)
|
||||
#define IDR_FACTORY_enum(name, number, type)
|
||||
#define IDR_FACTORY_bool(name, number, type)
|
||||
#define IDR_FACTORY_float(name, number, type)
|
||||
#define IDR_FACTORY_repeated_string(name, number, type)
|
||||
#define IDR_FACTORY_repeated_uint32(name, number, type)
|
||||
#define IDR_FACTORY_repeated_uint64(name, number, type)
|
||||
#define IDR_FACTORY_message(name, number, type) case number: return new type;
|
||||
#define IDR_FACTORY_repeated_message(name, number, type) case number: return new type;
|
||||
|
||||
#define IDR_REQUIRE(kind, name, number, type) if (!HasField(number)) return false;
|
||||
#define IDR_DECLARE(kind, name, number, type) IDR_DECL_##kind(name, number, type)
|
||||
#define IDR_KIND(kind, name, number, type) IDR_KIND_##kind(name, number, type)
|
||||
#define IDR_FACTORY(kind, name, number, type) IDR_FACTORY_##kind(name, number, type)
|
||||
|
||||
#define IDR_CLASS(name, fields, required, extras) \
|
||||
class name : public internal::ReportMessage { \
|
||||
public: \
|
||||
name() {} \
|
||||
name(const name& other) : internal::ReportMessage() { MergeFrom(other); } \
|
||||
name& operator=(const name& other) { if (this != &other) CopyFrom(other); return *this; } \
|
||||
std::string GetTypeName() const override { return "idreports." #name; } \
|
||||
name* New() const override { return new name; } \
|
||||
static const name& default_instance() { static const name value; return value; } \
|
||||
void CopyFrom(const name& other) { internal::ReportMessage::CopyFrom(other); } \
|
||||
void MergeFrom(const name& other) { internal::ReportMessage::MergeFrom(other); } \
|
||||
void Swap(name* other) { internal::ReportMessage::Swap(other); } \
|
||||
fields(IDR_DECLARE) \
|
||||
extras \
|
||||
protected: \
|
||||
internal::FieldKind FieldKindForNumber(int number) const override { switch (number) { fields(IDR_KIND) default: return internal::FIELD_UNKNOWN; } } \
|
||||
google::protobuf::MessageLite* NewMessageForField(int number) const override { switch (number) { case -1: return 0; fields(IDR_FACTORY) default: return 0; } } \
|
||||
bool HasRequiredFields() const override { required(IDR_REQUIRE) return true; } \
|
||||
};
|
||||
|
||||
#define IDR_NO_FIELDS(F)
|
||||
#define IDR_NO_EXTRAS
|
||||
|
||||
#define IDR_BIGUINT_FIELDS(F) \
|
||||
F(uint64, lowpart, 1, int) \
|
||||
F(uint64, highpart, 2, int)
|
||||
#define IDR_BIGUINT_REQUIRED(F) IDR_BIGUINT_FIELDS(F)
|
||||
IDR_CLASS(BigUInt, IDR_BIGUINT_FIELDS, IDR_BIGUINT_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_ATTACHMENT_FIELDS(F) \
|
||||
F(string, attachment, 1, int) \
|
||||
F(string, filename, 2, int)
|
||||
#define IDR_ATTACHMENT_REQUIRED(F) IDR_ATTACHMENT_FIELDS(F)
|
||||
IDR_CLASS(Attachment, IDR_ATTACHMENT_FIELDS, IDR_ATTACHMENT_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_MEMORYINFO_FIELDS(F) \
|
||||
F(uint32, inuse, 1, int) F(uint32, physicalmb, 2, int) \
|
||||
F(uint32, physicalfree, 3, int) F(uint32, pagingfile, 4, int) \
|
||||
F(uint32, pagingfree, 5, int) F(uint32, useraddress, 6, int) \
|
||||
F(uint32, userfree, 7, int)
|
||||
#define IDR_MEMORYINFO_REQUIRED(F) IDR_MEMORYINFO_FIELDS(F)
|
||||
IDR_CLASS(MemoryInfo, IDR_MEMORYINFO_FIELDS, IDR_MEMORYINFO_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_EXCEPTIONINFO_FIELDS(F) \
|
||||
F(string, expcode, 1, int) F(enum, expflags, 2, int) \
|
||||
F(string, expaddress, 3, int)
|
||||
#define IDR_EXCEPTIONINFO_REQUIRED(F) IDR_EXCEPTIONINFO_FIELDS(F)
|
||||
IDR_CLASS(ExceptionInfo, IDR_EXCEPTIONINFO_FIELDS, IDR_EXCEPTIONINFO_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_CALLSTACKLINE_FIELDS(F) \
|
||||
F(string, functioncall, 1, int) F(int32, line, 2, int) \
|
||||
F(int32, bytepos, 3, int) F(string, filename, 4, int) \
|
||||
F(string, address, 5, int)
|
||||
#define IDR_CALLSTACKLINE_REQUIRED(F) IDR_CALLSTACKLINE_FIELDS(F)
|
||||
IDR_CLASS(CallstackLine, IDR_CALLSTACKLINE_FIELDS, IDR_CALLSTACKLINE_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_MAPWARNING_FIELDS(F) \
|
||||
F(string, message, 1, int) F(repeated_uint32, marks, 2, int) \
|
||||
F(uint32, count, 3, int)
|
||||
#define IDR_MAPWARNING_REQUIRED(F) F(string, message, 1, int)
|
||||
IDR_CLASS(MapWarning, IDR_MAPWARNING_FIELDS, IDR_MAPWARNING_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_CPUINFO_FIELDS(F) \
|
||||
F(string, cpuid, 1, int) F(int32, packages, 2, int) \
|
||||
F(int32, cores, 3, int) F(int32, logical, 4, int) \
|
||||
F(float, frequency, 5, int)
|
||||
#define IDR_CPUINFO_REQUIRED(F) IDR_CPUINFO_FIELDS(F)
|
||||
IDR_CLASS(CPUInfo, IDR_CPUINFO_FIELDS, IDR_CPUINFO_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_REGISTERINFO_FIELDS(F) \
|
||||
F(string, edi, 1, int) F(string, esi, 2, int) F(string, eax, 3, int) \
|
||||
F(string, ebx, 4, int) F(string, ecx, 5, int) F(string, edx, 6, int) \
|
||||
F(string, eip, 7, int) F(string, ebp, 8, int) F(string, esp, 9, int) \
|
||||
F(string, eflags, 10, int) F(string, segcs, 11, int) \
|
||||
F(string, segss, 12, int) F(enum, platform, 13, TargetPlatform)
|
||||
#define IDR_REGISTERINFO_REQUIRED(F) IDR_REGISTERINFO_FIELDS(F)
|
||||
IDR_CLASS(RegisterInfo, IDR_REGISTERINFO_FIELDS, IDR_REGISTERINFO_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_HEAPCATEGORY_FIELDS(F) \
|
||||
F(string, categoryname, 1, int) F(uint32, totalever, 2, int) \
|
||||
F(uint32, totalcurrent, 3, int) F(uint32, totalsize, 4, int) \
|
||||
F(uint32, totalwaste, 5, int)
|
||||
#define IDR_HEAPCATEGORY_REQUIRED(F) IDR_HEAPCATEGORY_FIELDS(F)
|
||||
IDR_CLASS(HeapCategoryUsage, IDR_HEAPCATEGORY_FIELDS, IDR_HEAPCATEGORY_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_XBOXVERSION_FIELDS(F) \
|
||||
F(uint32, major, 1, int) F(uint32, minor, 2, int) \
|
||||
F(uint32, build, 3, int) F(uint32, qfe, 4, int)
|
||||
#define IDR_XBOXVERSION_REQUIRED(F) IDR_XBOXVERSION_FIELDS(F)
|
||||
IDR_CLASS(Xbox360VersionInfo, IDR_XBOXVERSION_FIELDS, IDR_XBOXVERSION_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_XENONMEMORY_FIELDS(F) \
|
||||
F(uint32, totalpages, 1, int) F(uint32, availablepages, 2, int) \
|
||||
F(uint32, stackpages, 3, int) F(uint32, virtualpagetablepages, 4, int) \
|
||||
F(uint32, systempagetablepages, 5, int) F(uint32, poolpages, 6, int) \
|
||||
F(uint32, virtualmappedpages, 7, int) F(uint32, imagepages, 8, int) \
|
||||
F(uint32, filecachepages, 9, int) F(uint32, contiguouspages, 10, int) \
|
||||
F(uint32, debuggerpages, 11, int)
|
||||
#define IDR_XENONMEMORY_REQUIRED(F) IDR_XENONMEMORY_FIELDS(F)
|
||||
IDR_CLASS(XenonMemoryInfo, IDR_XENONMEMORY_FIELDS, IDR_XENONMEMORY_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_PS3EXCEPTION_FIELDS(F) \
|
||||
F(string, exceptionname, 1, int) F(uint64, exceptioncode, 2, int) \
|
||||
F(uint64, dar, 3, int)
|
||||
#define IDR_PS3EXCEPTION_REQUIRED(F) \
|
||||
F(string, exceptionname, 1, int) F(uint64, exceptioncode, 2, int)
|
||||
IDR_CLASS(PS3ExceptionInfo, IDR_PS3EXCEPTION_FIELDS, IDR_PS3EXCEPTION_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_PS3VMREGION_FIELDS(F) \
|
||||
F(string, regionname, 1, int) F(int32, virtualsizeinbytes, 3, int) \
|
||||
F(int32, uncommittedpages, 4, int) F(int32, physicalpages, 5, int) \
|
||||
F(int32, diskpages, 6, int) F(int32, maxpages, 7, int) \
|
||||
F(int32, committedpages, 8, int)
|
||||
#define IDR_PS3VMREGION_REQUIRED(F) IDR_PS3VMREGION_FIELDS(F)
|
||||
IDR_CLASS(PS3VMRegionStats, IDR_PS3VMREGION_FIELDS, IDR_PS3VMREGION_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_LOGEVENT_FIELDS(F) \
|
||||
F(string, timestamp, 1, int) F(enum, severity, 2, LogEvent_Severity) \
|
||||
F(string, message, 3, int) F(repeated_string, tags, 4, int) \
|
||||
F(string, data, 5, int) F(string, datatype, 6, int) \
|
||||
F(repeated_message, externs, 7, Attachment) \
|
||||
F(enum, platform, 8, TargetPlatform)
|
||||
#define IDR_LOGEVENT_REQUIRED(F) \
|
||||
F(string, timestamp, 1, int) F(string, message, 3, int)
|
||||
#define IDR_LOGEVENT_EXTRAS \
|
||||
typedef LogEvent_Severity Severity; \
|
||||
static const Severity SEV_EMERGENCY = LogEvent_Severity_SEV_EMERGENCY; \
|
||||
static const Severity SEV_ALERT = LogEvent_Severity_SEV_ALERT; \
|
||||
static const Severity SEV_CRITICAL = LogEvent_Severity_SEV_CRITICAL; \
|
||||
static const Severity SEV_ERROR = LogEvent_Severity_SEV_ERROR; \
|
||||
static const Severity SEV_WARNING = LogEvent_Severity_SEV_WARNING; \
|
||||
static const Severity SEV_NOTICE = LogEvent_Severity_SEV_NOTICE; \
|
||||
static const Severity SEV_INFO = LogEvent_Severity_SEV_INFO; \
|
||||
static const Severity SEV_DEBUG = LogEvent_Severity_SEV_DEBUG; \
|
||||
static const Severity SEV_TRASH = LogEvent_Severity_SEV_TRASH;
|
||||
IDR_CLASS(LogEvent, IDR_LOGEVENT_FIELDS, IDR_LOGEVENT_REQUIRED, IDR_LOGEVENT_EXTRAS)
|
||||
|
||||
#define IDR_ASSERTREPORT_FIELDS(F) \
|
||||
F(string, mapname, 1, int) F(string, filename, 2, int) \
|
||||
F(uint32, line, 3, int) F(string, expression, 4, int) \
|
||||
F(string, username, 5, int)
|
||||
#define IDR_ASSERTREPORT_REQUIRED(F) \
|
||||
F(string, mapname, 1, int) F(string, filename, 2, int) \
|
||||
F(uint32, line, 3, int) F(string, expression, 4, int)
|
||||
IDR_CLASS(AssertReport, IDR_ASSERTREPORT_FIELDS, IDR_ASSERTREPORT_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_MAPREPORT_FIELDS(F) \
|
||||
F(string, mapname, 1, int) F(uint32, loadtime, 2, int) \
|
||||
F(string, username, 3, int) F(string, machinename, 4, int) \
|
||||
F(string, platform, 5, int) F(string, game, 6, int) \
|
||||
F(string, buildversion, 7, int) F(string, buildtimestamp, 8, int) \
|
||||
F(repeated_message, warnings, 9, MapWarning)
|
||||
#define IDR_MAPREPORT_REQUIRED(F) \
|
||||
F(string, mapname, 1, int) F(uint32, loadtime, 2, int) \
|
||||
F(string, platform, 5, int) F(string, game, 6, int) \
|
||||
F(string, buildversion, 7, int) F(string, buildtimestamp, 8, int)
|
||||
IDR_CLASS(MapReport, IDR_MAPREPORT_FIELDS, IDR_MAPREPORT_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_VIEWNOTE_FIELDS(F) \
|
||||
F(string, game, 1, int) F(string, username, 2, int) \
|
||||
F(string, reportedby, 3, int) F(bool, issingleplayer, 4, int) \
|
||||
F(string, platform, 5, int) F(int32, buildnumbermajor, 6, int) \
|
||||
F(int32, buildnumberminor, 7, int) F(string, timestamp, 9, int) \
|
||||
F(string, launchcommand, 10, int) F(string, vtfilepath, 11, int) \
|
||||
F(string, vtfilepathvmtroverride, 12, int) F(string, bugtitle, 13, int) \
|
||||
F(string, tasktype, 14, int) F(string, reprosteps, 15, int) \
|
||||
F(string, details, 16, int) F(string, severity, 17, int) \
|
||||
F(string, mappath, 19, int) F(string, priority, 20, int) \
|
||||
F(message, attachment, 22, Attachment)
|
||||
#define IDR_VIEWNOTE_REQUIRED(F) \
|
||||
F(string, game, 1, int) F(string, username, 2, int) \
|
||||
F(string, reportedby, 3, int) F(bool, issingleplayer, 4, int) \
|
||||
F(string, platform, 5, int) F(int32, buildnumbermajor, 6, int) \
|
||||
F(int32, buildnumberminor, 7, int) F(string, timestamp, 9, int) \
|
||||
F(string, launchcommand, 10, int) F(string, vtfilepath, 11, int) \
|
||||
F(string, vtfilepathvmtroverride, 12, int) F(string, bugtitle, 13, int) \
|
||||
F(string, tasktype, 14, int) F(string, reprosteps, 15, int) \
|
||||
F(string, details, 16, int) F(string, severity, 17, int)
|
||||
IDR_CLASS(ViewNoteReport, IDR_VIEWNOTE_FIELDS, IDR_VIEWNOTE_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_STRINGID_FIELDS(F) F(repeated_string, stringids, 1, int)
|
||||
IDR_CLASS(StringIDReferenceReport, IDR_STRINGID_FIELDS, IDR_NO_FIELDS, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_HEAPUSAGE_FIELDS(F) \
|
||||
F(enum, heaptype, 1, HeapType) F(uint32, totalever, 2, int) \
|
||||
F(uint32, totalcurrent, 3, int) F(uint32, totalsize, 4, int) \
|
||||
F(uint32, totalwaste, 5, int) \
|
||||
F(repeated_message, categories, 6, HeapCategoryUsage)
|
||||
#define IDR_HEAPUSAGE_REQUIRED(F) \
|
||||
F(enum, heaptype, 1, HeapType) F(uint32, totalever, 2, int) \
|
||||
F(uint32, totalcurrent, 3, int) F(uint32, totalsize, 4, int) \
|
||||
F(uint32, totalwaste, 5, int)
|
||||
IDR_CLASS(HeapUsage, IDR_HEAPUSAGE_FIELDS, IDR_HEAPUSAGE_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_XBOXKIT_FIELDS(F) \
|
||||
F(enum, kittype, 1, Xbox360ConsoleType) \
|
||||
F(message, basekernelversion, 2, Xbox360VersionInfo) \
|
||||
F(message, kernelversion, 3, Xbox360VersionInfo) \
|
||||
F(message, xdkversion, 4, Xbox360VersionInfo) \
|
||||
F(uint32, systeminfoflags, 5, int) F(string, consolename, 6, int)
|
||||
#define IDR_XBOXKIT_REQUIRED(F) IDR_XBOXKIT_FIELDS(F)
|
||||
IDR_CLASS(Xbox360KitInfo, IDR_XBOXKIT_FIELDS, IDR_XBOXKIT_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_XENONREGISTER_FIELDS(F) \
|
||||
F(string, msr, 1, int) F(string, iar, 2, int) F(string, lr, 3, int) \
|
||||
F(string, ctr, 4, int) F(string, fpscr, 5, int) \
|
||||
F(repeated_string, fpr, 6, int) F(string, cr, 7, int) \
|
||||
F(string, xer, 8, int) F(repeated_string, gpr, 9, int) \
|
||||
F(string, vscr, 10, int) F(repeated_string, vr, 11, int)
|
||||
#define IDR_XENONREGISTER_REQUIRED(F) \
|
||||
F(string, msr, 1, int) F(string, iar, 2, int) F(string, lr, 3, int) \
|
||||
F(string, ctr, 4, int) F(string, fpscr, 5, int) \
|
||||
F(string, cr, 7, int) F(string, xer, 8, int) F(string, vscr, 10, int)
|
||||
IDR_CLASS(XenonRegisterInfo, IDR_XENONREGISTER_FIELDS, IDR_XENONREGISTER_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_PPUREGISTER_FIELDS(F) \
|
||||
F(repeated_uint64, gpr, 1, int) F(uint32, cr, 2, int) \
|
||||
F(uint64, xer, 3, int) F(uint64, lr, 4, int) F(uint64, ctr, 5, int) \
|
||||
F(uint64, pc, 6, int) F(repeated_uint64, fpr, 7, int) \
|
||||
F(uint32, fpscr, 8, int) F(repeated_message, vr, 9, BigUInt) \
|
||||
F(message, vscr, 10, BigUInt)
|
||||
#define IDR_PPUREGISTER_REQUIRED(F) \
|
||||
F(uint32, cr, 2, int) F(uint64, xer, 3, int) F(uint64, lr, 4, int) \
|
||||
F(uint64, ctr, 5, int) F(uint64, pc, 6, int) \
|
||||
F(uint32, fpscr, 8, int) F(message, vscr, 10, BigUInt)
|
||||
IDR_CLASS(PPURegisterInfo, IDR_PPUREGISTER_FIELDS, IDR_PPUREGISTER_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_SPUREGISTER_FIELDS(F) \
|
||||
F(repeated_message, gpr, 1, BigUInt) F(uint32, npc, 2, int) \
|
||||
F(repeated_message, fpscr, 3, BigUInt) F(uint32, srr0, 4, int) \
|
||||
F(uint32, spu_status, 5, int) F(uint64, spu_cfg, 6, int) \
|
||||
F(uint32, mb_stat, 7, int) F(uint32, ppu_mb, 8, int) \
|
||||
F(repeated_uint32, spu_mb, 9, int) F(uint32, decrementer, 10, int) \
|
||||
F(repeated_uint64, mfc_cq_sr, 11, int)
|
||||
#define IDR_SPUREGISTER_REQUIRED(F) \
|
||||
F(uint32, npc, 2, int) F(uint32, srr0, 4, int) \
|
||||
F(uint32, spu_status, 5, int) F(uint64, spu_cfg, 6, int) \
|
||||
F(uint32, mb_stat, 7, int) F(uint32, ppu_mb, 8, int) \
|
||||
F(uint32, decrementer, 10, int)
|
||||
IDR_CLASS(SPURegisterInfo, IDR_SPUREGISTER_FIELDS, IDR_SPUREGISTER_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_PS3MEMORY_FIELDS(F) \
|
||||
F(uint32, totalsystemmemory, 1, int) F(uint32, usedsystemmemory, 2, int) \
|
||||
F(uint32, availablesystemmemory, 3, int) F(uint32, unaccountedfor, 4, int) \
|
||||
F(uint32, pmem_total, 5, int) F(uint32, pmem_used, 6, int) \
|
||||
F(uint64, page_in, 7, int) F(uint64, page_out, 8, int) \
|
||||
F(uint64, page_fault_ppu, 9, int) F(uint64, page_fault_spu, 10, int) \
|
||||
F(repeated_message, regions, 11, PS3VMRegionStats)
|
||||
#define IDR_PS3MEMORY_REQUIRED(F) \
|
||||
F(uint32, totalsystemmemory, 1, int) F(uint32, usedsystemmemory, 2, int) \
|
||||
F(uint32, availablesystemmemory, 3, int) F(uint32, unaccountedfor, 4, int) \
|
||||
F(uint32, pmem_total, 5, int) F(uint32, pmem_used, 6, int) \
|
||||
F(uint64, page_in, 7, int) F(uint64, page_out, 8, int) \
|
||||
F(uint64, page_fault_ppu, 9, int) F(uint64, page_fault_spu, 10, int)
|
||||
IDR_CLASS(PS3MemoryInfo, IDR_PS3MEMORY_FIELDS, IDR_PS3MEMORY_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_COMMON_CRASH_FIELDS(F) \
|
||||
F(string, game, 1, int) F(string, username, 2, int) \
|
||||
F(string, platform, 3, int) F(int32, buildnumbermajor, 4, int) \
|
||||
F(int32, buildnumberminor, 5, int) F(string, buildstring, 6, int) \
|
||||
F(string, timestamp, 7, int) F(string, launchcommand, 8, int) \
|
||||
F(string, vtfilepath, 9, int) F(string, vtfilepathvmtroverride, 10, int)
|
||||
|
||||
#define IDR_CRASHREPORT_FIELDS(F) \
|
||||
IDR_COMMON_CRASH_FIELDS(F) F(string, bugtitle, 11, int) \
|
||||
F(string, reprosteps, 12, int) F(string, details, 13, int) \
|
||||
F(string, severity, 14, int) F(string, component, 15, int) \
|
||||
F(string, mappath, 16, int) F(string, priority, 17, int) \
|
||||
F(string, localfilename, 18, int) F(string, dmppath, 19, int) \
|
||||
F(repeated_message, callstack, 20, CallstackLine) \
|
||||
F(message, exception, 21, ExceptionInfo) F(message, registers, 22, RegisterInfo) \
|
||||
F(message, attachment, 23, Attachment) F(message, cpuinfo, 24, CPUInfo) \
|
||||
F(message, meminfo, 25, MemoryInfo) \
|
||||
F(repeated_string, consolehistory, 26, int)
|
||||
#define IDR_CRASHREPORT_REQUIRED(F) \
|
||||
IDR_COMMON_CRASH_FIELDS(F) F(string, bugtitle, 11, int) \
|
||||
F(string, reprosteps, 12, int) F(string, details, 13, int) \
|
||||
F(string, severity, 14, int) F(string, component, 15, int) \
|
||||
F(string, localfilename, 18, int)
|
||||
IDR_CLASS(CrashReport, IDR_CRASHREPORT_FIELDS, IDR_CRASHREPORT_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_XBOXCRASH_FIELDS(F) \
|
||||
IDR_COMMON_CRASH_FIELDS(F) F(string, mappath, 11, int) \
|
||||
F(message, kitinfo, 12, Xbox360KitInfo) \
|
||||
F(repeated_string, callstack, 13, int) \
|
||||
F(message, exception, 14, ExceptionInfo) \
|
||||
F(message, registers, 15, XenonRegisterInfo) \
|
||||
F(message, consolememory, 16, XenonMemoryInfo) \
|
||||
F(message, titlememory, 17, XenonMemoryInfo)
|
||||
#define IDR_XBOXCRASH_REQUIRED(F) \
|
||||
IDR_COMMON_CRASH_FIELDS(F) F(message, kitinfo, 12, Xbox360KitInfo)
|
||||
IDR_CLASS(Xbox360CrashReport, IDR_XBOXCRASH_FIELDS, IDR_XBOXCRASH_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_PS3CRASH_FIELDS(F) \
|
||||
IDR_COMMON_CRASH_FIELDS(F) F(string, mappath, 11, int) \
|
||||
F(repeated_uint64, callstack, 12, int) \
|
||||
F(message, exception, 13, PS3ExceptionInfo) \
|
||||
F(message, ppuregisters, 14, PPURegisterInfo) \
|
||||
F(repeated_message, spuregisters, 15, SPURegisterInfo) \
|
||||
F(message, memoryinfo, 16, PS3MemoryInfo)
|
||||
#define IDR_PS3CRASH_REQUIRED(F) IDR_COMMON_CRASH_FIELDS(F)
|
||||
IDR_CLASS(PS3CrashReport, IDR_PS3CRASH_FIELDS, IDR_PS3CRASH_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#define IDR_CRASHRESULT_FIELDS(F) F(int64, bugid, 1, int)
|
||||
#define IDR_CRASHRESULT_REQUIRED(F) IDR_CRASHRESULT_FIELDS(F)
|
||||
IDR_CLASS(CrashReportResult, IDR_CRASHRESULT_FIELDS, IDR_CRASHRESULT_REQUIRED, IDR_NO_EXTRAS)
|
||||
|
||||
#undef IDR_CLASS
|
||||
#undef IDR_DECLARE
|
||||
#undef IDR_KIND
|
||||
#undef IDR_FACTORY
|
||||
#undef IDR_REQUIRE
|
||||
|
||||
void protobuf_AddDesc_reports_2eproto();
|
||||
void protobuf_ShutdownFile_reports_2eproto();
|
||||
|
||||
} // namespace idreports
|
||||
@@ -0,0 +1,180 @@
|
||||
#include "bitblockallocator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
idBitBlockAllocator::idBitBlockAllocator(const int blocksWide,
|
||||
const int blocksHigh)
|
||||
: width(std::max(0, blocksWide)), height(std::max(0, blocksHigh)),
|
||||
bits(nullptr) {
|
||||
const std::size_t count = static_cast<std::size_t>(width)
|
||||
* static_cast<std::size_t>(height);
|
||||
if (count != 0) {
|
||||
bits = static_cast<unsigned char*>(std::calloc((count + 7u) / 8u, 1));
|
||||
}
|
||||
}
|
||||
|
||||
idBitBlockAllocator::~idBitBlockAllocator() {
|
||||
std::free(bits);
|
||||
}
|
||||
|
||||
void idBitBlockAllocator::Clear() {
|
||||
if (bits != nullptr) {
|
||||
const std::size_t count = static_cast<std::size_t>(width)
|
||||
* static_cast<std::size_t>(height);
|
||||
std::memset(bits, 0, (count + 7u) / 8u);
|
||||
}
|
||||
}
|
||||
|
||||
bool idBitBlockAllocator::Get(const int x, const int y) const {
|
||||
if (bits == nullptr || x < 0 || y < 0 || x >= width || y >= height) {
|
||||
return false;
|
||||
}
|
||||
const int bit = y * width + x;
|
||||
return (bits[bit >> 3] & (1u << (bit & 7))) != 0;
|
||||
}
|
||||
|
||||
void idBitBlockAllocator::Set(const int x, const int y) {
|
||||
if (bits == nullptr || x < 0 || y < 0 || x >= width || y >= height) {
|
||||
return;
|
||||
}
|
||||
const int bit = y * width + x;
|
||||
bits[bit >> 3] |= static_cast<unsigned char>(1u << (bit & 7));
|
||||
}
|
||||
|
||||
void idBitBlockAllocator::FillBlock(const int x, const int y,
|
||||
const int w, const int h) {
|
||||
for (int row = 0; row < h; ++row) {
|
||||
for (int column = 0; column < w; ++column) {
|
||||
Set(x + column, y + row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void idBitBlockAllocator::FillBitBlock(const int x, const int y,
|
||||
const idBitBlockAllocator& block) {
|
||||
for (int row = 0; row < block.height; ++row) {
|
||||
for (int column = 0; column < block.width; ++column) {
|
||||
if (block.Get(column, row)) {
|
||||
Set(x + column, y + row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool idBitBlockAllocator::TestBlock(const int x, const int y, const int w,
|
||||
const int h, int* const fail) const {
|
||||
if (w < 0 || h < 0 || x < 0 || y < 0 || x + w > width || y + h > height) {
|
||||
return false;
|
||||
}
|
||||
if (fail != nullptr && fail[0] >= x && fail[0] < x + w
|
||||
&& fail[1] >= y && fail[1] < y + h) {
|
||||
return false;
|
||||
}
|
||||
for (int row = 0; row < h; ++row) {
|
||||
for (int column = w - 1; column >= 0; --column) {
|
||||
if (Get(x + column, y + row)) {
|
||||
if (fail != nullptr) {
|
||||
fail[0] = x + column;
|
||||
fail[1] = y + row;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool idBitBlockAllocator::TestBitBlock(const int x, const int y,
|
||||
const idBitBlockAllocator& block) const {
|
||||
if (x < 0 || y < 0 || x + block.width > width || y + block.height > height) {
|
||||
return false;
|
||||
}
|
||||
for (int row = 0; row < block.height; ++row) {
|
||||
for (int column = 0; column < block.width; ++column) {
|
||||
if (block.Get(column, row) && Get(x + column, y + row)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool idBitBlockAllocator::FindBlockLinear(int& x, int& y, const int w,
|
||||
const int h) {
|
||||
int fail[2] = { -1, -1 };
|
||||
for (y = 0; y <= height - h; ++y) {
|
||||
for (x = 0; x <= width - w; ++x) {
|
||||
if (TestBlock(x, y, w, h, fail)) {
|
||||
FillBlock(x, y, w, h);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool idBitBlockAllocator::FindBitBlockLinear(int& x, int& y,
|
||||
const idBitBlockAllocator& block) {
|
||||
for (y = 0; y <= height - block.height; ++y) {
|
||||
for (x = 0; x <= width - block.width; ++x) {
|
||||
if (TestBitBlock(x, y, block)) {
|
||||
FillBitBlock(x, y, block);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SeparateBits(const int value, int& x, int& y) {
|
||||
x = 0;
|
||||
y = 0;
|
||||
const unsigned int bits = static_cast<unsigned int>(value);
|
||||
for (unsigned int index = 0; index < 16; ++index) {
|
||||
x |= static_cast<int>(((bits >> (index * 2)) & 1u) << index);
|
||||
y |= static_cast<int>(((bits >> (index * 2 + 1)) & 1u) << index);
|
||||
}
|
||||
}
|
||||
|
||||
void SeparateBits2(const int bits, int& x, int& y) {
|
||||
SeparateBits(bits, x, y);
|
||||
}
|
||||
|
||||
bool idBitBlockAllocator::FindBlockInterleaved(int& x, int& y, const int w,
|
||||
const int h, int& searchPoint, const bool naturallyAlign) {
|
||||
if (w > width || h > height || w < 0 || h < 0) {
|
||||
return false;
|
||||
}
|
||||
if (searchPoint < 0) {
|
||||
searchPoint = 0;
|
||||
}
|
||||
const int count = width * height;
|
||||
int fail[2] = { -1, -1 };
|
||||
const auto nextPowerOfTwo = [](const int value) {
|
||||
int power = 1;
|
||||
while (power < std::max(1, value)) {
|
||||
power <<= 1;
|
||||
}
|
||||
return power;
|
||||
};
|
||||
const int alignX = nextPowerOfTwo(w);
|
||||
const int alignY = nextPowerOfTwo(h);
|
||||
for (; searchPoint < count; ++searchPoint) {
|
||||
const int interleaved = naturallyAlign
|
||||
? searchPoint
|
||||
: ((searchPoint >> 1) ^ searchPoint);
|
||||
SeparateBits2(interleaved, x, y);
|
||||
if (naturallyAlign
|
||||
&& (x / alignX != (x + std::max(0, w - 1)) / alignX
|
||||
|| y / alignY != (y + std::max(0, h - 1)) / alignY)) {
|
||||
continue;
|
||||
}
|
||||
if (TestBlock(x, y, w, h, fail)) {
|
||||
FillBlock(x, y, w, h);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class idBitBlockAllocator {
|
||||
public:
|
||||
idBitBlockAllocator(int blocksWide, int blocksHigh);
|
||||
~idBitBlockAllocator();
|
||||
|
||||
idBitBlockAllocator(const idBitBlockAllocator&) = delete;
|
||||
idBitBlockAllocator& operator=(const idBitBlockAllocator&) = delete;
|
||||
|
||||
void Clear();
|
||||
void FillBlock(int x, int y, int w, int h);
|
||||
void FillBitBlock(int x, int y, const idBitBlockAllocator& block);
|
||||
bool TestBlock(int x, int y, int w, int h, int* fail = nullptr) const;
|
||||
bool TestBitBlock(int x, int y, const idBitBlockAllocator& block) const;
|
||||
bool FindBlockLinear(int& x, int& y, int w, int h);
|
||||
bool FindBitBlockLinear(int& x, int& y, const idBitBlockAllocator& block);
|
||||
bool FindBlockInterleaved(int& x, int& y, int w, int h,
|
||||
int& searchPoint, bool naturallyAlign);
|
||||
|
||||
int Width() const { return width; }
|
||||
int Height() const { return height; }
|
||||
bool Get(int x, int y) const;
|
||||
|
||||
private:
|
||||
int width;
|
||||
int height;
|
||||
unsigned char* bits;
|
||||
|
||||
void Set(int x, int y);
|
||||
};
|
||||
|
||||
void SeparateBits(int bits, int& x, int& y);
|
||||
void SeparateBits2(int bits, int& x, int& y);
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idBitBlockAllocator) == 12,
|
||||
"Recovered idBitBlockAllocator ABI changed");
|
||||
#endif
|
||||
@@ -0,0 +1,5 @@
|
||||
#include "locklessqueue.h"
|
||||
|
||||
// The recovered translation unit contains the engine's producer/consumer
|
||||
// stress command. The queue algorithms are template code in the header; their
|
||||
// portable regression coverage lives in idlib_containers_test.
|
||||
@@ -0,0 +1,139 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <thread>
|
||||
|
||||
template<typename type, int queueSize>
|
||||
class idLocklessQueueSingleProdCons {
|
||||
public:
|
||||
idLocklessQueueSingleProdCons()
|
||||
: queue{}, queueStart(0), queueEnd(0) {
|
||||
static_assert(queueSize > 1 && (queueSize & (queueSize - 1)) == 0,
|
||||
"Lockless queue size must be a power of two");
|
||||
}
|
||||
|
||||
void Add(type* element) {
|
||||
int end = queueEnd.load(std::memory_order_relaxed);
|
||||
while (((end + 1) & Mask())
|
||||
== queueStart.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
end = queueEnd.load(std::memory_order_relaxed);
|
||||
}
|
||||
queue[end] = element;
|
||||
queueEnd.store((end + 1) & Mask(), std::memory_order_release);
|
||||
}
|
||||
|
||||
type* Next() {
|
||||
const int start = queueStart.load(std::memory_order_relaxed);
|
||||
if (start == queueEnd.load(std::memory_order_acquire)) {
|
||||
return nullptr;
|
||||
}
|
||||
type* const result = queue[start];
|
||||
queueStart.store((start + 1) & Mask(), std::memory_order_release);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IsEmpty() const {
|
||||
return queueStart.load(std::memory_order_acquire)
|
||||
== queueEnd.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
private:
|
||||
type* queue[queueSize];
|
||||
std::atomic<int> queueStart;
|
||||
alignas(128) std::atomic<int> queueEnd;
|
||||
|
||||
static constexpr int Mask() { return queueSize - 1; }
|
||||
};
|
||||
|
||||
template<typename type, int queueSize>
|
||||
class idLocklessQueueMultiProdCons {
|
||||
public:
|
||||
idLocklessQueueMultiProdCons()
|
||||
: queue{}, queueStart(0), queueFetched(0), queueEnd(0), queueAlloced(0) {
|
||||
static_assert(queueSize > 1 && (queueSize & (queueSize - 1)) == 0,
|
||||
"Lockless queue size must be a power of two");
|
||||
static_assert(sizeof(std::atomic<type*>) == sizeof(type*),
|
||||
"PC atomics changed the recovered queue slot layout");
|
||||
for (int index = 0; index < queueSize; ++index) {
|
||||
queue[index].store(nullptr, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
void Add(type* element) {
|
||||
int allocated;
|
||||
for (;;) {
|
||||
allocated = queueAlloced.load(std::memory_order_relaxed);
|
||||
while ((((allocated + 1)
|
||||
^ queueFetched.load(std::memory_order_acquire)) & Mask()) == 0) {
|
||||
std::this_thread::yield();
|
||||
allocated = queueAlloced.load(std::memory_order_relaxed);
|
||||
}
|
||||
if (queueAlloced.compare_exchange_weak(allocated, allocated + 1,
|
||||
std::memory_order_acq_rel, std::memory_order_relaxed)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
queue[allocated & Mask()].store(element, std::memory_order_release);
|
||||
AdvancePublishedEnd();
|
||||
}
|
||||
|
||||
type* Next() {
|
||||
int start = queueStart.load(std::memory_order_relaxed);
|
||||
for (;;) {
|
||||
if (start == queueEnd.load(std::memory_order_acquire)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (queueStart.compare_exchange_weak(start, start + 1,
|
||||
std::memory_order_acq_rel, std::memory_order_relaxed)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
type* const result = queue[start & Mask()].exchange(
|
||||
nullptr, std::memory_order_acq_rel
|
||||
);
|
||||
AdvanceFetched();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IsEmpty() const {
|
||||
return queueStart.load(std::memory_order_acquire)
|
||||
== queueEnd.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<type*> queue[queueSize];
|
||||
std::atomic<int> queueStart;
|
||||
alignas(128) std::atomic<int> queueFetched;
|
||||
alignas(128) std::atomic<int> queueEnd;
|
||||
alignas(128) std::atomic<int> queueAlloced;
|
||||
|
||||
static constexpr int Mask() { return queueSize - 1; }
|
||||
|
||||
void AdvancePublishedEnd() {
|
||||
int end = queueEnd.load(std::memory_order_relaxed);
|
||||
while (end != queueAlloced.load(std::memory_order_acquire)
|
||||
&& queue[end & Mask()].load(std::memory_order_acquire) != nullptr) {
|
||||
if (!queueEnd.compare_exchange_weak(end, end + 1,
|
||||
std::memory_order_release, std::memory_order_relaxed)) {
|
||||
continue;
|
||||
}
|
||||
end = queueEnd.load(std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvanceFetched() {
|
||||
int fetched = queueFetched.load(std::memory_order_relaxed);
|
||||
while (fetched != queueStart.load(std::memory_order_acquire)
|
||||
&& queue[fetched & Mask()].load(std::memory_order_acquire) == nullptr) {
|
||||
if (!queueFetched.compare_exchange_weak(fetched, fetched + 1,
|
||||
std::memory_order_release, std::memory_order_relaxed)) {
|
||||
continue;
|
||||
}
|
||||
fetched = queueFetched.load(std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
#include "runningaverage.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
|
||||
idRunningAverage::idRunningAverage()
|
||||
: maxNum(0)
|
||||
, current(0)
|
||||
, vals{nullptr, 0, 0, 16, 5, 0} {
|
||||
}
|
||||
|
||||
idRunningAverage::~idRunningAverage() {
|
||||
std::free(vals.list);
|
||||
}
|
||||
|
||||
float idRunningAverage::GetAverage() {
|
||||
if (vals.num == 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float total = 0.0f;
|
||||
for (int index = 0; index < vals.num; ++index) {
|
||||
total += vals.list[index];
|
||||
}
|
||||
return total / static_cast<float>(vals.num);
|
||||
}
|
||||
|
||||
float idRunningAverage::GetMin() {
|
||||
if (vals.num == 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float minimum = vals.list[0];
|
||||
for (int index = 1; index < vals.num; ++index) {
|
||||
minimum = std::min(minimum, vals.list[index]);
|
||||
}
|
||||
return minimum;
|
||||
}
|
||||
|
||||
float idRunningAverage::GetMax() {
|
||||
if (vals.num == 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float maximum = vals.list[0];
|
||||
for (int index = 1; index < vals.num; ++index) {
|
||||
maximum = std::max(maximum, vals.list[index]);
|
||||
}
|
||||
return maximum;
|
||||
}
|
||||
|
||||
void idRunningAverage::Init(const int num) {
|
||||
current = 0;
|
||||
|
||||
if (num <= 0) {
|
||||
vals.num = 0;
|
||||
maxNum = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (vals.size < num) {
|
||||
float* const replacement = static_cast<float*>(
|
||||
std::malloc(sizeof(float) * static_cast<std::size_t>(num))
|
||||
);
|
||||
if (replacement == nullptr) {
|
||||
vals.num = 0;
|
||||
maxNum = 0;
|
||||
return;
|
||||
}
|
||||
std::free(vals.list);
|
||||
vals.list = replacement;
|
||||
vals.size = num;
|
||||
}
|
||||
|
||||
vals.num = 0;
|
||||
maxNum = num;
|
||||
}
|
||||
|
||||
void idRunningAverage::Add(const float val) {
|
||||
if (maxNum <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vals.num != maxNum) {
|
||||
++vals.num;
|
||||
current = vals.num - 1;
|
||||
} else {
|
||||
++current;
|
||||
if (current >= maxNum) {
|
||||
current = 0;
|
||||
}
|
||||
}
|
||||
|
||||
vals.list[current] = val;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// Recovered layout of idList<float, 5> from tungsten.exe.h type 12501. This
|
||||
// private storage facade avoids importing the newer BFG container definition
|
||||
// while retaining the original Win32 field widths and offsets.
|
||||
struct idRunningAverageFloatList {
|
||||
float* list;
|
||||
int num;
|
||||
int size;
|
||||
std::int16_t granularity;
|
||||
std::uint8_t memTag;
|
||||
std::uint8_t listStatic;
|
||||
};
|
||||
|
||||
static_assert(
|
||||
sizeof(idRunningAverageFloatList) == 16,
|
||||
"Recovered idList<float, 5> layout changed"
|
||||
);
|
||||
|
||||
// tungsten.exe.h type 12505.
|
||||
class idRunningAverage {
|
||||
public:
|
||||
idRunningAverage();
|
||||
~idRunningAverage();
|
||||
|
||||
idRunningAverage(const idRunningAverage&) = delete;
|
||||
idRunningAverage& operator=(const idRunningAverage&) = delete;
|
||||
|
||||
float GetAverage();
|
||||
float GetMin();
|
||||
float GetMax();
|
||||
void Init(int num);
|
||||
void Add(float val);
|
||||
|
||||
private:
|
||||
int maxNum;
|
||||
int current;
|
||||
idRunningAverageFloatList vals;
|
||||
};
|
||||
|
||||
static_assert(sizeof(idRunningAverage) == 24, "idRunningAverage ABI changed");
|
||||
@@ -0,0 +1,5 @@
|
||||
#include "runtimeexpression.h"
|
||||
|
||||
// The recovered RuntimeExpression.cpp is the console regression harness. The
|
||||
// reusable parser/evaluator is template code in RuntimeExpression.h and is
|
||||
// covered by idlib_runtimeexpression_test on the PC port.
|
||||
@@ -0,0 +1,380 @@
|
||||
#pragma once
|
||||
|
||||
#include "text/str.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
|
||||
template<typename varType, typename varContextType>
|
||||
class idRuntimeExpression {
|
||||
public:
|
||||
enum { MAX_NODES = 253, INVALID_INDEX = 255 };
|
||||
|
||||
class ExpNode {
|
||||
public:
|
||||
enum op_t {
|
||||
OP_NONE = 0,
|
||||
OP_VAL,
|
||||
OP_VAR,
|
||||
OP_MUL,
|
||||
OP_DIV,
|
||||
OP_MAX,
|
||||
OP_MIN,
|
||||
OP_LERP,
|
||||
OP_CLAMP,
|
||||
OP_SUB,
|
||||
OP_ADD
|
||||
};
|
||||
|
||||
ExpNode()
|
||||
: coef(1.0f), op(OP_NONE), parent(INVALID_INDEX),
|
||||
var0(INVALID_INDEX), var1(INVALID_INDEX), var2(INVALID_INDEX),
|
||||
varId() {
|
||||
}
|
||||
|
||||
float coef;
|
||||
op_t op;
|
||||
std::uint8_t parent;
|
||||
std::uint8_t var0;
|
||||
std::uint8_t var1;
|
||||
std::uint8_t var2;
|
||||
varType varId;
|
||||
};
|
||||
|
||||
idRuntimeExpression()
|
||||
: root(INVALID_INDEX), nodes(nullptr), numNodes(0), capacity(0),
|
||||
granularity(5), memTag(5), listStatic(0) {
|
||||
}
|
||||
|
||||
~idRuntimeExpression() {
|
||||
Clear();
|
||||
}
|
||||
|
||||
idRuntimeExpression(const idRuntimeExpression&) = delete;
|
||||
idRuntimeExpression& operator=(const idRuntimeExpression&) = delete;
|
||||
|
||||
bool Parse(const char* expression, varContextType& context) {
|
||||
Clear();
|
||||
const char* cursor = expression == nullptr ? "" : expression;
|
||||
const int parsedRoot = ParseAddSub(cursor, context);
|
||||
SkipWhitespace(cursor);
|
||||
if (parsedRoot == INVALID_INDEX || *cursor != '\0') {
|
||||
Clear();
|
||||
return false;
|
||||
}
|
||||
root = parsedRoot;
|
||||
return true;
|
||||
}
|
||||
|
||||
float Eval(varContextType& context) const {
|
||||
return root == INVALID_INDEX ? 0.0f : EvalNode(root, context);
|
||||
}
|
||||
|
||||
idStr PrintExp() const {
|
||||
return root == INVALID_INDEX ? idStr() : PrintNode(root);
|
||||
}
|
||||
|
||||
int NumNodes() const { return numNodes; }
|
||||
|
||||
void Clear() {
|
||||
if (nodes != nullptr) {
|
||||
for (int index = 0; index < capacity; ++index) {
|
||||
nodes[index].~ExpNode();
|
||||
}
|
||||
std::free(nodes);
|
||||
}
|
||||
root = INVALID_INDEX;
|
||||
nodes = nullptr;
|
||||
numNodes = 0;
|
||||
capacity = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
int root;
|
||||
ExpNode* nodes;
|
||||
int numNodes;
|
||||
int capacity;
|
||||
std::int16_t granularity;
|
||||
std::uint8_t memTag;
|
||||
std::uint8_t listStatic;
|
||||
|
||||
static void SkipWhitespace(const char*& cursor) {
|
||||
while (std::isspace(static_cast<unsigned char>(*cursor)) != 0) {
|
||||
++cursor;
|
||||
}
|
||||
}
|
||||
|
||||
bool Grow() {
|
||||
if (capacity >= MAX_NODES) {
|
||||
return false;
|
||||
}
|
||||
const int newCapacity = std::min(static_cast<int>(MAX_NODES),
|
||||
capacity + (granularity > 0 ? granularity : 5));
|
||||
ExpNode* const replacement = static_cast<ExpNode*>(
|
||||
std::malloc(sizeof(ExpNode) * static_cast<std::size_t>(newCapacity))
|
||||
);
|
||||
if (replacement == nullptr) {
|
||||
return false;
|
||||
}
|
||||
for (int index = 0; index < newCapacity; ++index) {
|
||||
new (&replacement[index]) ExpNode();
|
||||
}
|
||||
for (int index = 0; index < numNodes; ++index) {
|
||||
replacement[index] = nodes[index];
|
||||
}
|
||||
if (nodes != nullptr) {
|
||||
for (int index = 0; index < capacity; ++index) {
|
||||
nodes[index].~ExpNode();
|
||||
}
|
||||
std::free(nodes);
|
||||
}
|
||||
nodes = replacement;
|
||||
capacity = newCapacity;
|
||||
return true;
|
||||
}
|
||||
|
||||
int AddNode(const typename ExpNode::op_t op, const int child0 = INVALID_INDEX,
|
||||
const int child1 = INVALID_INDEX, const int child2 = INVALID_INDEX,
|
||||
const float coefficient = 1.0f, const varType* variable = nullptr) {
|
||||
if (numNodes == capacity && !Grow()) {
|
||||
return INVALID_INDEX;
|
||||
}
|
||||
const int index = numNodes++;
|
||||
ExpNode& node = nodes[index];
|
||||
node.op = op;
|
||||
node.coef = coefficient;
|
||||
node.var0 = static_cast<std::uint8_t>(child0);
|
||||
node.var1 = static_cast<std::uint8_t>(child1);
|
||||
node.var2 = static_cast<std::uint8_t>(child2);
|
||||
if (variable != nullptr) {
|
||||
node.varId = *variable;
|
||||
}
|
||||
if (child0 != INVALID_INDEX) nodes[child0].parent = static_cast<std::uint8_t>(index);
|
||||
if (child1 != INVALID_INDEX) nodes[child1].parent = static_cast<std::uint8_t>(index);
|
||||
if (child2 != INVALID_INDEX) nodes[child2].parent = static_cast<std::uint8_t>(index);
|
||||
return index;
|
||||
}
|
||||
|
||||
int ParseAddSub(const char*& cursor, varContextType& context) {
|
||||
int left = ParseMulDiv(cursor, context);
|
||||
while (left != INVALID_INDEX) {
|
||||
SkipWhitespace(cursor);
|
||||
const char operation = *cursor;
|
||||
if (operation != '+' && operation != '-') break;
|
||||
++cursor;
|
||||
const int right = ParseMulDiv(cursor, context);
|
||||
if (right == INVALID_INDEX) return INVALID_INDEX;
|
||||
left = AddNode(operation == '+' ? ExpNode::OP_ADD : ExpNode::OP_SUB,
|
||||
left, right);
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
int ParseMulDiv(const char*& cursor, varContextType& context) {
|
||||
int left = ParseUnary(cursor, context);
|
||||
while (left != INVALID_INDEX) {
|
||||
SkipWhitespace(cursor);
|
||||
const char operation = *cursor;
|
||||
if (operation != '*' && operation != '/') break;
|
||||
++cursor;
|
||||
const int right = ParseUnary(cursor, context);
|
||||
if (right == INVALID_INDEX) return INVALID_INDEX;
|
||||
left = AddNode(operation == '*' ? ExpNode::OP_MUL : ExpNode::OP_DIV,
|
||||
left, right);
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
int ParseUnary(const char*& cursor, varContextType& context) {
|
||||
SkipWhitespace(cursor);
|
||||
if (*cursor == '+') {
|
||||
++cursor;
|
||||
return ParseUnary(cursor, context);
|
||||
}
|
||||
if (*cursor == '-') {
|
||||
++cursor;
|
||||
const int child = ParseUnary(cursor, context);
|
||||
return child == INVALID_INDEX ? INVALID_INDEX
|
||||
: AddNode(ExpNode::OP_MUL, child, INVALID_INDEX,
|
||||
INVALID_INDEX, -1.0f);
|
||||
}
|
||||
return ParsePrimary(cursor, context);
|
||||
}
|
||||
|
||||
int ParsePrimary(const char*& cursor, varContextType& context) {
|
||||
SkipWhitespace(cursor);
|
||||
if (*cursor == '(') {
|
||||
++cursor;
|
||||
const int result = ParseAddSub(cursor, context);
|
||||
SkipWhitespace(cursor);
|
||||
if (*cursor != ')') return INVALID_INDEX;
|
||||
++cursor;
|
||||
return result;
|
||||
}
|
||||
if (std::isdigit(static_cast<unsigned char>(*cursor)) != 0
|
||||
|| *cursor == '.') {
|
||||
char* numberEnd = nullptr;
|
||||
const float value = std::strtof(cursor, &numberEnd);
|
||||
if (numberEnd == cursor) return INVALID_INDEX;
|
||||
cursor = numberEnd;
|
||||
return AddNode(ExpNode::OP_VAL, INVALID_INDEX, INVALID_INDEX,
|
||||
INVALID_INDEX, value);
|
||||
}
|
||||
|
||||
char name[128];
|
||||
int nameLength = 0;
|
||||
while ((std::isalnum(static_cast<unsigned char>(*cursor)) != 0
|
||||
|| *cursor == '_' || *cursor == '.') && nameLength < 127) {
|
||||
name[nameLength++] = *cursor++;
|
||||
}
|
||||
name[nameLength] = '\0';
|
||||
if (nameLength == 0) return INVALID_INDEX;
|
||||
SkipWhitespace(cursor);
|
||||
if (*cursor != '(') {
|
||||
varType variable;
|
||||
if (!context.LookUpVar(name, variable)) return INVALID_INDEX;
|
||||
return AddNode(ExpNode::OP_VAR, INVALID_INDEX, INVALID_INDEX,
|
||||
INVALID_INDEX, 1.0f, &variable);
|
||||
}
|
||||
|
||||
++cursor;
|
||||
const int first = ParseAddSub(cursor, context);
|
||||
SkipWhitespace(cursor);
|
||||
if (first == INVALID_INDEX || *cursor != ',') return INVALID_INDEX;
|
||||
++cursor;
|
||||
const int second = ParseAddSub(cursor, context);
|
||||
if (second == INVALID_INDEX) return INVALID_INDEX;
|
||||
int third = INVALID_INDEX;
|
||||
SkipWhitespace(cursor);
|
||||
if (*cursor == ',') {
|
||||
++cursor;
|
||||
third = ParseAddSub(cursor, context);
|
||||
if (third == INVALID_INDEX) return INVALID_INDEX;
|
||||
}
|
||||
SkipWhitespace(cursor);
|
||||
if (*cursor != ')') return INVALID_INDEX;
|
||||
++cursor;
|
||||
|
||||
typename ExpNode::op_t operation = ExpNode::OP_NONE;
|
||||
if (std::strcmp(name, "max") == 0 && third == INVALID_INDEX) operation = ExpNode::OP_MAX;
|
||||
else if (std::strcmp(name, "min") == 0 && third == INVALID_INDEX) operation = ExpNode::OP_MIN;
|
||||
else if (std::strcmp(name, "lerp") == 0 && third != INVALID_INDEX) operation = ExpNode::OP_LERP;
|
||||
else if (std::strcmp(name, "clamp") == 0 && third != INVALID_INDEX) operation = ExpNode::OP_CLAMP;
|
||||
if (operation == ExpNode::OP_NONE) return INVALID_INDEX;
|
||||
return AddNode(operation, first, second, third);
|
||||
}
|
||||
|
||||
float EvalNode(const int index, varContextType& context) const {
|
||||
const ExpNode& node = nodes[index];
|
||||
const float first = node.var0 == INVALID_INDEX ? 0.0f
|
||||
: EvalNode(node.var0, context);
|
||||
const float second = node.var1 == INVALID_INDEX ? 0.0f
|
||||
: EvalNode(node.var1, context);
|
||||
const float third = node.var2 == INVALID_INDEX ? 0.0f
|
||||
: EvalNode(node.var2, context);
|
||||
float value = 0.0f;
|
||||
switch (node.op) {
|
||||
case ExpNode::OP_VAL: value = 1.0f; break;
|
||||
case ExpNode::OP_VAR: value = context.GetVar(node.varId); break;
|
||||
case ExpNode::OP_MUL:
|
||||
value = node.var1 == INVALID_INDEX ? first : first * second;
|
||||
break;
|
||||
case ExpNode::OP_DIV: value = first / second; break;
|
||||
case ExpNode::OP_MAX: value = std::max(first, second); break;
|
||||
case ExpNode::OP_MIN: value = std::min(first, second); break;
|
||||
case ExpNode::OP_LERP: value = first + (second - first) * third; break;
|
||||
case ExpNode::OP_CLAMP: value = std::max(first, std::min(second, third)); break;
|
||||
case ExpNode::OP_SUB: value = first - second; break;
|
||||
case ExpNode::OP_ADD: value = first + second; break;
|
||||
default: value = 0.0f; break;
|
||||
}
|
||||
return node.coef * value;
|
||||
}
|
||||
|
||||
idStr PrintNode(const int index) const {
|
||||
const ExpNode& node = nodes[index];
|
||||
if (node.op == ExpNode::OP_VAL) {
|
||||
char number[64];
|
||||
std::snprintf(number, sizeof(number), "%g", node.coef);
|
||||
return idStr(number);
|
||||
}
|
||||
if (node.op == ExpNode::OP_VAR) {
|
||||
idStr result = node.varId.GetStr();
|
||||
if (node.coef != 1.0f) {
|
||||
char prefix[64];
|
||||
std::snprintf(prefix, sizeof(prefix), "%g*", node.coef);
|
||||
idStr scaled(prefix);
|
||||
scaled.Append(result);
|
||||
return scaled;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
const char* opName = nullptr;
|
||||
switch (node.op) {
|
||||
case ExpNode::OP_MUL: opName = "*"; break;
|
||||
case ExpNode::OP_DIV: opName = "/"; break;
|
||||
case ExpNode::OP_SUB: opName = "-"; break;
|
||||
case ExpNode::OP_ADD: opName = "+"; break;
|
||||
case ExpNode::OP_MAX: opName = "max"; break;
|
||||
case ExpNode::OP_MIN: opName = "min"; break;
|
||||
case ExpNode::OP_LERP: opName = "lerp"; break;
|
||||
case ExpNode::OP_CLAMP: opName = "clamp"; break;
|
||||
default: return idStr();
|
||||
}
|
||||
idStr result;
|
||||
if (node.op == ExpNode::OP_MAX || node.op == ExpNode::OP_MIN
|
||||
|| node.op == ExpNode::OP_LERP || node.op == ExpNode::OP_CLAMP) {
|
||||
result.Append(opName);
|
||||
result.Append('(');
|
||||
result.Append(PrintNode(node.var0));
|
||||
result.Append(", ");
|
||||
result.Append(PrintNode(node.var1));
|
||||
if (node.var2 != INVALID_INDEX) {
|
||||
result.Append(", ");
|
||||
result.Append(PrintNode(node.var2));
|
||||
}
|
||||
result.Append(')');
|
||||
} else if (node.var1 == INVALID_INDEX) {
|
||||
char coefficient[64];
|
||||
std::snprintf(coefficient, sizeof(coefficient), "%g*", node.coef);
|
||||
result.Append(coefficient);
|
||||
result.Append(PrintNode(node.var0));
|
||||
return result;
|
||||
} else {
|
||||
result.Append('(');
|
||||
result.Append(PrintNode(node.var0));
|
||||
result.Append(' ');
|
||||
result.Append(opName);
|
||||
result.Append(' ');
|
||||
result.Append(PrintNode(node.var1));
|
||||
result.Append(')');
|
||||
}
|
||||
if (node.coef != 1.0f) {
|
||||
char coefficient[64];
|
||||
std::snprintf(coefficient, sizeof(coefficient), "%g*", node.coef);
|
||||
idStr scaled(coefficient);
|
||||
scaled.Append(result);
|
||||
return scaled;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
struct idRuntimeExpressionLayoutVar {
|
||||
int index;
|
||||
idStr GetStr() const { return idStr(); }
|
||||
};
|
||||
struct idRuntimeExpressionLayoutContext {
|
||||
bool LookUpVar(const char*, idRuntimeExpressionLayoutVar&) { return false; }
|
||||
float GetVar(const idRuntimeExpressionLayoutVar&) const { return 0.0f; }
|
||||
};
|
||||
static_assert(sizeof(idRuntimeExpression<idRuntimeExpressionLayoutVar,
|
||||
idRuntimeExpressionLayoutContext>) == 20,
|
||||
"Recovered idRuntimeExpression ABI changed");
|
||||
#endif
|
||||
@@ -0,0 +1,171 @@
|
||||
#pragma once
|
||||
|
||||
#include "idlib/text/str.h"
|
||||
|
||||
#include <new>
|
||||
|
||||
enum scFileType_t {
|
||||
SCT_TEXT = 0,
|
||||
SCT_BINARY,
|
||||
SCT_SYMLINK,
|
||||
SCT_APPLE,
|
||||
SCT_RESOURCE,
|
||||
SCT_UNICODE,
|
||||
SCT_UTF16,
|
||||
SCT_NONE
|
||||
};
|
||||
|
||||
enum scFileStatus_t {
|
||||
SCF_NOTMANAGED = 0,
|
||||
SCF_NOTIMPORTED,
|
||||
SCF_CHECKEDOUT,
|
||||
SCF_CHECKEDOUT_PLUS,
|
||||
SCF_CHECKEDOUT_BYOTHER,
|
||||
SCF_CHECKEDOUT_BYOTHER_EXCLUSIVE,
|
||||
SCF_CHECKEDIN
|
||||
};
|
||||
|
||||
template<class type, int tag = 5>
|
||||
class idSourceControlList {
|
||||
public:
|
||||
explicit idSourceControlList(int listGranularity = 16)
|
||||
: list(nullptr), num(0), size(0),
|
||||
granularity(static_cast<short>(listGranularity > 0
|
||||
? listGranularity : 16)), memTag(tag), listStatic(0) {}
|
||||
|
||||
idSourceControlList(const idSourceControlList& other)
|
||||
: idSourceControlList(other.granularity) {
|
||||
EnsureCapacity(other.num);
|
||||
for (int index = 0; index < other.num; ++index) Append(other[index]);
|
||||
}
|
||||
|
||||
~idSourceControlList() { delete[] list; }
|
||||
|
||||
idSourceControlList& operator=(const idSourceControlList& other) {
|
||||
if (this == &other) return *this;
|
||||
Clear();
|
||||
granularity = other.granularity;
|
||||
EnsureCapacity(other.num);
|
||||
for (int index = 0; index < other.num; ++index) Append(other[index]);
|
||||
return *this;
|
||||
}
|
||||
|
||||
int Append(const type& value) {
|
||||
if (!EnsureCapacity(num + 1)) return -1;
|
||||
list[num] = value;
|
||||
return num++;
|
||||
}
|
||||
|
||||
void Clear() { num = 0; }
|
||||
int Num() const { return num; }
|
||||
type& operator[](int index) { return list[index]; }
|
||||
const type& operator[](int index) const { return list[index]; }
|
||||
|
||||
private:
|
||||
bool EnsureCapacity(int amount) {
|
||||
if (amount <= size) return true;
|
||||
int newSize = size == 0 ? granularity : size;
|
||||
while (newSize < amount) newSize += granularity;
|
||||
type* const replacement = new (std::nothrow) type[newSize];
|
||||
if (replacement == nullptr) return false;
|
||||
for (int index = 0; index < num; ++index) replacement[index] = list[index];
|
||||
delete[] list;
|
||||
list = replacement;
|
||||
size = newSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
type* list;
|
||||
int num;
|
||||
int size;
|
||||
short granularity;
|
||||
unsigned char memTag;
|
||||
unsigned char listStatic;
|
||||
};
|
||||
|
||||
using idSourceControlFileList = idSourceControlList<idStr, 5>;
|
||||
|
||||
class idSourceControl {
|
||||
public:
|
||||
struct idSourceControlWorkspace {
|
||||
idStr name;
|
||||
idStr lastAccessTime;
|
||||
idStr owner;
|
||||
idStr host;
|
||||
};
|
||||
using workspaceList_t = idSourceControlList<idSourceControlWorkspace, 5>;
|
||||
|
||||
virtual ~idSourceControl() = default;
|
||||
virtual bool Init() { return false; }
|
||||
virtual void Shutdown() {}
|
||||
virtual void SetSilentCheckOut(bool) {}
|
||||
virtual bool GetSilentCheckOut() const { return false; }
|
||||
virtual void SetSilentCheckIn(bool) {}
|
||||
virtual bool GetSilentCheckIn() const { return false; }
|
||||
virtual bool IsConnected() const { return false; }
|
||||
virtual bool IsInitialized() const { return false; }
|
||||
virtual int CheckOut(const idSourceControlFileList&) { return 0; }
|
||||
virtual int UndoCheckOut(const idSourceControlFileList&) { return 0; }
|
||||
virtual int Import(const idSourceControlFileList&, bool, bool,
|
||||
scFileType_t) { return 0; }
|
||||
virtual int GetLatest(const idSourceControlFileList&, bool) { return 0; }
|
||||
virtual int CheckIn(const idSourceControlFileList&, const idStr&,
|
||||
bool) { return 0; }
|
||||
virtual int Delete(const idSourceControlFileList&) { return 0; }
|
||||
virtual int UpdateFolder(const char*) { return 0; }
|
||||
virtual scFileStatus_t GetFileStatus(const idStr&) {
|
||||
return SCF_NOTMANAGED;
|
||||
}
|
||||
virtual bool GetFileVersion(const idStr&, int&, int&) { return false; }
|
||||
virtual int GetNumCheckOutUsers(const idStr&) { return 0; }
|
||||
virtual void GetCheckOutUser(const idStr&, int, char* output,
|
||||
int outputSize) {
|
||||
if (output != nullptr && outputSize > 0) output[0] = '\0';
|
||||
}
|
||||
virtual void GetUsernameForFileVersion(const idStr&, int,
|
||||
idStr& output) { output.Clear(); }
|
||||
virtual void GetCurrentWorkspace(idSourceControlWorkspace& output) {
|
||||
output = idSourceControlWorkspace();
|
||||
}
|
||||
virtual void GetAvailableWorkspaces(workspaceList_t& output) {
|
||||
output.Clear();
|
||||
}
|
||||
virtual void SetWorkspace(const char*) {}
|
||||
virtual void SetWorkspace(idSourceControlWorkspace&) {}
|
||||
virtual bool IsWorkspaceValid() { return false; }
|
||||
virtual idStr GetUsername() { return idStr(); }
|
||||
|
||||
int CheckOut(const idStr& file) {
|
||||
idSourceControlFileList files(1);
|
||||
files.Append(file);
|
||||
return CheckOut(files);
|
||||
}
|
||||
|
||||
int Import(const idStr& file, bool keepCheckedOut, bool submit,
|
||||
scFileType_t fileType) {
|
||||
idSourceControlFileList files(1);
|
||||
files.Append(file);
|
||||
return Import(files, keepCheckedOut, submit, fileType);
|
||||
}
|
||||
|
||||
int GetLatest(const idStr& file, bool force) {
|
||||
idSourceControlFileList files(1);
|
||||
files.Append(file);
|
||||
return GetLatest(files, force);
|
||||
}
|
||||
|
||||
int Delete(const idStr& file) {
|
||||
idSourceControlFileList files(1);
|
||||
files.Append(file);
|
||||
return Delete(files);
|
||||
}
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idSourceControlFileList) == 16,
|
||||
"Recovered source-control list ABI changed");
|
||||
static_assert(sizeof(idSourceControl::idSourceControlWorkspace) == 128,
|
||||
"Recovered source-control workspace ABI changed");
|
||||
static_assert(sizeof(idSourceControl) == 4,
|
||||
"Recovered idSourceControl ABI changed");
|
||||
#endif
|
||||
@@ -0,0 +1,208 @@
|
||||
#include "sys_alloc.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <malloc.h>
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
namespace {
|
||||
struct allocationRecord_t {
|
||||
allocationRecord_t* next;
|
||||
void* pointer;
|
||||
unsigned int bytes;
|
||||
memTag_t tag;
|
||||
heapType_t heap;
|
||||
const char* location;
|
||||
};
|
||||
|
||||
SRWLOCK allocationLock = SRWLOCK_INIT;
|
||||
allocationRecord_t* allocations = nullptr;
|
||||
std::atomic<unsigned long long> allocatedBytes(0);
|
||||
std::atomic<idOutOfMemoryCallback> outOfMemoryCallback(nullptr);
|
||||
thread_local heapType_t currentHeap = HEAP_SYSTEMHEAP;
|
||||
thread_local heapType_t pushedHeap = HEAP_SYSTEMHEAP;
|
||||
thread_local int heapStackDepth = 0;
|
||||
|
||||
const char* const tagNames[TAG_NUM_TAGS] = {
|
||||
"UNSET", "STATIC_EXE", "RESOURCE_GRAPH", "DEBUG", "NEW", "IDLIST",
|
||||
"TEMP", "STRING", "ATOMIC_STRING", "BITARRAY", "MATH", "LEXER",
|
||||
"COMPILER", "COLLISION", "COLLISION_QUERY", "CLIPMODEL", "MORPH",
|
||||
"MD6_MISC", "MD6_NODES", "MD6", "MD6_ANIMS", "MD6_LIPSYNC",
|
||||
"MD6_JOINTCACHE", "MD6_MESHES", "MD6_JOINTBUFFERS", "MD6_BLENDSTACK",
|
||||
"MD6_JOINTMODS", "MD6_COLLISION", "MD6_ANIMEVENTS", "MD6_PHASE_TRACK",
|
||||
"ANIMATION", "ANIMATION_DEBUG", "DECL_ANIMWEB", "ANIMWEB", "IMAGE",
|
||||
"DXIMAGE", "VIRTUALTEXTURE", "AAS", "SOUND", "SOUND_BSP", "SOUND_DATA",
|
||||
"SOUND_STREAM", "SOUND_MULTISTREAM", "SOUND_SAMPLETABLES", "IDLIB",
|
||||
"TRIANGLES", "DECL", "DECLTEXT", "FILE", "CVAR", "PAGEFILECACHE",
|
||||
"IDCLASS", "PRESENTABLE", "FOLIAGE", "WATER", "MEMORY_MAPPED_FILE",
|
||||
"RENDERPARM", "NETWORKING", "SCRIPT", "FXPHYSICS", "LWO", "RENDERWORLD",
|
||||
"RENDERER", "AI_GAMESTATE", "EVENTS", "VOICEOVER", "VOICETRACK_EVENTS",
|
||||
"VOICETRACK_FRAMEREFS", "VOICETRACK_PHONEMES", "VISEMESET_VISEMES",
|
||||
"VISEMESET_PHONEMES", "AF", "SWF", "GUI", "GUI_MODEL", "FUNC_CALLBACK",
|
||||
"MENU", "GAME", "HASHINDEX", "PARTICLE", "EFFECT_PARTICLE", "CLOTH",
|
||||
"ANIMTAGS", "IK", "STATICMODEL", "RENDERMODEL", "DXBUFFER", "TOOLS",
|
||||
"CLOUD", "AMQP", "RENDERPROG", "HASHTABLE", "AI_FSM", "AI_VISCACHE",
|
||||
"AI_SEARCH", "AI_OBSTACLE", "JOBLIST", "TRANSPARENCY", "DETAIL",
|
||||
"RESOURCE", "FILE_RESOURCE", "RESOURCE_BGL", "RESOURCE_BGL_RING",
|
||||
"RESOURCE_BGL_OVERSIZE", "RESOURCE_MGR", "PVS", "DEFERRED_VIS", "FIBER",
|
||||
"SUPERSCRIPT", "FX", "SAVEGAMES", "AI_TRANSITIONS", "AI_STATEDATA",
|
||||
"AI_COMBATANIM", "TYPEINFO", "DAMAGEDECAL", "TABLE", "VIDEO", "NAVPOWER",
|
||||
"LANGDICT", "SPLINE", "BINK", "FONTS", "EVENT_LISTENER", "PHYSICAL_BLOCK",
|
||||
"DXOBJECT"
|
||||
};
|
||||
|
||||
void AddRecord(void* pointer, const unsigned int bytes, const memTag_t tag,
|
||||
const heapType_t heap, const char* location) {
|
||||
allocationRecord_t* const record = static_cast<allocationRecord_t*>(
|
||||
std::malloc(sizeof(allocationRecord_t))
|
||||
);
|
||||
if (record == nullptr) {
|
||||
return;
|
||||
}
|
||||
record->pointer = pointer;
|
||||
record->bytes = bytes;
|
||||
record->tag = tag;
|
||||
record->heap = heap;
|
||||
record->location = location;
|
||||
AcquireSRWLockExclusive(&allocationLock);
|
||||
record->next = allocations;
|
||||
allocations = record;
|
||||
ReleaseSRWLockExclusive(&allocationLock);
|
||||
allocatedBytes.fetch_add(bytes, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
unsigned int RemoveRecord(void* pointer) {
|
||||
unsigned int bytes = 0;
|
||||
AcquireSRWLockExclusive(&allocationLock);
|
||||
allocationRecord_t** link = &allocations;
|
||||
while (*link != nullptr) {
|
||||
if ((*link)->pointer == pointer) {
|
||||
allocationRecord_t* const record = *link;
|
||||
*link = record->next;
|
||||
bytes = record->bytes;
|
||||
std::free(record);
|
||||
break;
|
||||
}
|
||||
link = &(*link)->next;
|
||||
}
|
||||
ReleaseSRWLockExclusive(&allocationLock);
|
||||
if (bytes != 0) {
|
||||
allocatedBytes.fetch_sub(bytes, std::memory_order_relaxed);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
idMem mem;
|
||||
idMemLocal memLocal;
|
||||
|
||||
const char* GetMemTagName(const int tag) {
|
||||
return tag >= 0 && tag < TAG_NUM_TAGS ? tagNames[tag] : "<BAD TAG NAME>";
|
||||
}
|
||||
|
||||
void* idMem::AllocWithLocation(const char* const location,
|
||||
const unsigned int size, const memTag_t tag, const bool zeroBuffer,
|
||||
const align_t alignment, const heapType_t requestedHeap) {
|
||||
const std::size_t allocationSize = size == 0 ? 1u : size;
|
||||
const std::size_t requestedAlignment = static_cast<std::size_t>(alignment);
|
||||
const std::size_t safeAlignment = requestedAlignment < sizeof(void*)
|
||||
? sizeof(void*) : requestedAlignment;
|
||||
|
||||
void* pointer = _aligned_malloc(allocationSize, safeAlignment);
|
||||
if (pointer == nullptr) {
|
||||
idOutOfMemoryCallback callback = outOfMemoryCallback.load(
|
||||
std::memory_order_acquire
|
||||
);
|
||||
if (callback != nullptr && callback()) {
|
||||
pointer = _aligned_malloc(allocationSize, safeAlignment);
|
||||
}
|
||||
}
|
||||
if (pointer == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (zeroBuffer) {
|
||||
std::memset(pointer, 0, allocationSize);
|
||||
}
|
||||
const heapType_t usedHeap = requestedHeap == HEAP_DEFAULTHEAP
|
||||
? currentHeap : requestedHeap;
|
||||
AddRecord(pointer, size, tag, usedHeap, location);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
void idMem::Free(void* const pointer, const align_t) {
|
||||
if (pointer == nullptr) {
|
||||
return;
|
||||
}
|
||||
RemoveRecord(pointer);
|
||||
_aligned_free(pointer);
|
||||
}
|
||||
|
||||
int idMem::BytesCurrentlyAllocated() const {
|
||||
const unsigned long long bytes = allocatedBytes.load(std::memory_order_relaxed);
|
||||
return bytes > static_cast<unsigned long long>((std::numeric_limits<int>::max)())
|
||||
? (std::numeric_limits<int>::max)() : static_cast<int>(bytes);
|
||||
}
|
||||
|
||||
void idMem::InitMapHeap() {
|
||||
}
|
||||
|
||||
void idMem::ResetMapHeap() {
|
||||
// The PC port uses the process CRT heap for both logical heaps. Allocations
|
||||
// remain individually tracked so stale map allocations can still be found.
|
||||
}
|
||||
|
||||
void idMem::PushHeap(const heapType_t heapType) {
|
||||
if (heapStackDepth++ == 0) {
|
||||
pushedHeap = currentHeap;
|
||||
currentHeap = heapType == HEAP_DEFAULTHEAP ? HEAP_MAPHEAP : heapType;
|
||||
}
|
||||
}
|
||||
|
||||
void idMem::PopHeap() {
|
||||
if (heapStackDepth <= 0) {
|
||||
heapStackDepth = 0;
|
||||
return;
|
||||
}
|
||||
if (--heapStackDepth == 0) {
|
||||
currentHeap = pushedHeap;
|
||||
}
|
||||
}
|
||||
|
||||
bool idMem::IsGlobalHeap() const {
|
||||
return currentHeap == HEAP_SYSTEMHEAP;
|
||||
}
|
||||
|
||||
void idMem::SetOutOfMemoryCallback(const idOutOfMemoryCallback callback) {
|
||||
outOfMemoryCallback.store(callback, std::memory_order_release);
|
||||
}
|
||||
|
||||
idOutOfMemoryCallback idMem::GetOutOfMemoryCallback() const {
|
||||
return outOfMemoryCallback.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void idMem::WriteMemoryReport(const char* const directory,
|
||||
const char* const fileName) const {
|
||||
char path[MAX_PATH];
|
||||
const char* const safeDirectory = directory == nullptr ? "." : directory;
|
||||
const char* const safeFile = fileName == nullptr ? "memory_report.txt" : fileName;
|
||||
std::snprintf(path, sizeof(path), "%s\\%s", safeDirectory, safeFile);
|
||||
FILE* report = nullptr;
|
||||
if (fopen_s(&report, path, "w") != 0 || report == nullptr) {
|
||||
return;
|
||||
}
|
||||
std::fprintf(report, "bytes currently allocated: %d\n", BytesCurrentlyAllocated());
|
||||
AcquireSRWLockShared(&allocationLock);
|
||||
for (allocationRecord_t* record = allocations; record != nullptr;
|
||||
record = record->next) {
|
||||
std::fprintf(report, "%p %10u %-24s heap=%d %s\n", record->pointer,
|
||||
record->bytes, GetMemTagName(record->tag), record->heap,
|
||||
record->location == nullptr ? "<unknown>" : record->location);
|
||||
}
|
||||
ReleaseSRWLockShared(&allocationLock);
|
||||
std::fclose(report);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum memTag_t : int {
|
||||
TAG_UNSET = 0x00, TAG_STATIC_EXE, TAG_RESOURCE_GRAPH, TAG_DEBUG,
|
||||
TAG_NEW, TAG_IDLIST, TAG_TEMP, TAG_STRING, TAG_ATOMIC_STRING,
|
||||
TAG_BITARRAY, TAG_MATH, TAG_LEXER, TAG_COMPILER, TAG_COLLISION,
|
||||
TAG_COLLISION_QUERY, TAG_CLIPMODEL, TAG_MORPH, TAG_MD6_MISC,
|
||||
TAG_MD6_NODES, TAG_MD6, TAG_MD6_ANIMS, TAG_MD6_LIPSYNC,
|
||||
TAG_MD6_JOINTCACHE, TAG_MD6_MESHES, TAG_MD6_JOINTBUFFERS,
|
||||
TAG_MD6_BLENDSTACK, TAG_MD6_JOINTMODS, TAG_MD6_COLLISION,
|
||||
TAG_MD6_ANIMEVENTS, TAG_MD6_PHASE_TRACK, TAG_ANIMATION,
|
||||
TAG_ANIMATION_DEBUG, TAG_DECL_ANIMWEB, TAG_ANIMWEB, TAG_IMAGE,
|
||||
TAG_DXIMAGE, TAG_VIRTUALTEXTURE, TAG_AAS, TAG_SOUND, TAG_SOUND_BSP,
|
||||
TAG_SOUND_DATA, TAG_SOUND_STREAM, TAG_SOUND_MULTISTREAM,
|
||||
TAG_SOUND_SAMPLETABLES, TAG_IDLIB, TAG_TRIANGLES, TAG_DECL,
|
||||
TAG_DECLTEXT, TAG_FILE, TAG_CVAR, TAG_PAGEFILECACHE, TAG_IDCLASS,
|
||||
TAG_PRESENTABLE, TAG_FOLIAGE, TAG_WATER, TAG_MEMORY_MAPPED_FILE,
|
||||
TAG_RENDERPARM, TAG_NETWORKING, TAG_SCRIPT, TAG_FXPHYSICS, TAG_LWO,
|
||||
TAG_RENDERWORLD, TAG_RENDERER, TAG_AI_GAMESTATE, TAG_EVENTS,
|
||||
TAG_VOICEOVER, TAG_VOICETRACK_EVENTS, TAG_VOICETRACK_FRAMEREFS,
|
||||
TAG_VOICETRACK_PHONEMES, TAG_VISEMESET_VISEMES,
|
||||
TAG_VISEMESET_PHONEMES, TAG_AF, TAG_SWF, TAG_GUI, TAG_GUI_MODEL,
|
||||
TAG_FUNC_CALLBACK, TAG_MENU, TAG_GAME, TAG_HASHINDEX, TAG_PARTICLE,
|
||||
TAG_EFFECT_PARTICLE, TAG_CLOTH, TAG_ANIMTAGS, TAG_IK, TAG_STATICMODEL,
|
||||
TAG_RENDERMODEL, TAG_DXBUFFER, TAG_TOOLS, TAG_CLOUD, TAG_AMQP,
|
||||
TAG_RENDERPROG, TAG_HASHTABLE, TAG_AI_FSM, TAG_AI_VISCACHE,
|
||||
TAG_AI_SEARCH, TAG_AI_OBSTACLE, TAG_JOBLIST, TAG_TRANSPARENCY,
|
||||
TAG_DETAIL, TAG_RESOURCE, TAG_FILE_RESOURCE, TAG_RESOURCE_BGL,
|
||||
TAG_RESOURCE_BGL_RING, TAG_RESOURCE_BGL_OVERSIZE, TAG_RESOURCE_MGR,
|
||||
TAG_PVS, TAG_DEFERRED_VIS, TAG_FIBER, TAG_SUPERSCRIPT, TAG_FX,
|
||||
TAG_SAVEGAMES, TAG_AI_TRANSITIONS, TAG_AI_STATEDATA, TAG_AI_COMBATANIM,
|
||||
TAG_TYPEINFO, TAG_DAMAGEDECAL, TAG_TABLE, TAG_VIDEO, TAG_NAVPOWER,
|
||||
TAG_LANGDICT, TAG_SPLINE, TAG_BINK, TAG_FONTS, TAG_EVENT_LISTENER,
|
||||
TAG_PHYSICAL_BLOCK, TAG_DXOBJECT, TAG_NUM_TAGS
|
||||
};
|
||||
|
||||
enum align_t : int {
|
||||
ALIGN_16 = 0x10,
|
||||
ALIGN_128 = 0x80,
|
||||
ALIGN_1M = 0x100000
|
||||
};
|
||||
|
||||
enum heapType_t : int {
|
||||
HEAP_DEFAULTHEAP = -1,
|
||||
HEAP_SYSTEMHEAP = 0,
|
||||
HEAP_MAPHEAP = 1
|
||||
};
|
||||
|
||||
using idOutOfMemoryCallback = bool (*)();
|
||||
|
||||
class idMem {
|
||||
public:
|
||||
void* AllocWithLocation(const char* location, unsigned int size,
|
||||
memTag_t tag, bool zeroBuffer = false, align_t alignment = ALIGN_16,
|
||||
heapType_t heap = HEAP_DEFAULTHEAP);
|
||||
void Free(void* pointer, align_t alignment = ALIGN_16);
|
||||
|
||||
int BytesCurrentlyAllocated() const;
|
||||
void InitMapHeap();
|
||||
void ResetMapHeap();
|
||||
void PushHeap(heapType_t heapType);
|
||||
void PopHeap();
|
||||
bool IsGlobalHeap() const;
|
||||
|
||||
void SetOutOfMemoryCallback(idOutOfMemoryCallback callback);
|
||||
idOutOfMemoryCallback GetOutOfMemoryCallback() const;
|
||||
void WriteMemoryReport(const char* directory, const char* fileName) const;
|
||||
};
|
||||
|
||||
class idMemLocal : public idMem {
|
||||
};
|
||||
|
||||
extern idMem mem;
|
||||
extern idMemLocal memLocal;
|
||||
|
||||
const char* GetMemTagName(int tag);
|
||||
bool Sys_AllocWillUseMapHeap();
|
||||
void Sys_ReportHeaps();
|
||||
void ReportGlobalMemoryStatus();
|
||||
|
||||
void* Sys_Alloc(unsigned int size, memTag_t tag,
|
||||
align_t alignment = ALIGN_16,
|
||||
heapType_t heap = HEAP_DEFAULTHEAP);
|
||||
void Sys_Free(void* pointer);
|
||||
unsigned int Sys_GetStreamFileCacheUsage();
|
||||
unsigned int Sys_GetMemoryUsage();
|
||||
unsigned int Sys_GetFreeMemory();
|
||||
void Sys_WriteMemoryReport(const char* mapName, const char* version);
|
||||
void Sys_DumpMemory();
|
||||
|
||||
class idPhysicalMemoryBlock {
|
||||
public:
|
||||
idPhysicalMemoryBlock();
|
||||
|
||||
void Init(int bytesToAllocate);
|
||||
void RevertToDiscreteAllocations();
|
||||
void BeginResourceLoads();
|
||||
void EndResourceLoads(bool neverFreeAllocatedData);
|
||||
void* PhysicalAlloc(unsigned int bytes, int alignment, memTag_t tag);
|
||||
void* OverlayAlloc(unsigned int bytes, const char* name);
|
||||
void OverlayFree(void* pointer);
|
||||
bool AddressIsInReservedPhysicalMemoryBlock(const void* pointer) const;
|
||||
bool AddressIsInOverlayPhysicalMemoryBlock(const void* pointer) const;
|
||||
void ReportPhysicalMemoryBlock() const;
|
||||
void ReportUntouchedPhysicalMemory() const;
|
||||
|
||||
private:
|
||||
unsigned char* reservedPhysicalMemoryBlock;
|
||||
int totalBlockSize;
|
||||
int commonBytes;
|
||||
int overlayBytes;
|
||||
int cacheBytes;
|
||||
bool insideResourceBlockLoad;
|
||||
int physicalBytesAllocated;
|
||||
int imageBytesAllocated;
|
||||
int bufferBytesAllocated;
|
||||
int otherBytesAllocated;
|
||||
int alignmentWaste;
|
||||
int bytesForcedOutsideBlock;
|
||||
};
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(idPhysicalMemoryBlock) == 48,
|
||||
"Recovered idPhysicalMemoryBlock ABI changed");
|
||||
#endif
|
||||
|
||||
class idScopedGlobalHeap {
|
||||
public:
|
||||
idScopedGlobalHeap() { mem.PushHeap(HEAP_SYSTEMHEAP); }
|
||||
~idScopedGlobalHeap() { mem.PopHeap(); }
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "sys_mgrd.h"
|
||||
|
||||
// MGRD was an optional external memory-profiler transport selected with the
|
||||
// Xbox-era -mgrd switch. The PC recovery keeps the instrumentation API intact
|
||||
// while making it a no-op until a desktop profiler backend is selected.
|
||||
void RD_Init() {}
|
||||
void RD_CreateGPUHeaps(void*, unsigned int, void*, unsigned int) {}
|
||||
void RD_DestroyGPUHeaps() {}
|
||||
void RD_CreateMapHeap() {}
|
||||
void RD_DestroyMapHeap() {}
|
||||
void RD_MemAlloc(void*, unsigned int, unsigned int, int) {}
|
||||
void RD_MemFree(void*, int) {}
|
||||
void RD_EventBegin(const char*) {}
|
||||
void RD_EventEnd() {}
|
||||
void RD_Syncpoint(const char*) {}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
void RD_Init();
|
||||
void RD_CreateGPUHeaps(void* gpuMemory, unsigned int gpuBytes,
|
||||
void* systemMemory, unsigned int systemBytes);
|
||||
void RD_DestroyGPUHeaps();
|
||||
void RD_CreateMapHeap();
|
||||
void RD_DestroyMapHeap();
|
||||
void RD_MemAlloc(void* pointer, unsigned int size, unsigned int waste, int heap);
|
||||
void RD_MemFree(void* pointer, int heap);
|
||||
void RD_EventBegin(const char* name);
|
||||
void RD_EventEnd();
|
||||
void RD_Syncpoint(const char* name);
|
||||
|
||||
class idRDScopedEvent {
|
||||
public:
|
||||
explicit idRDScopedEvent(const char* name) { RD_EventBegin(name); }
|
||||
~idRDScopedEvent() { RD_EventEnd(); }
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#ifndef __SYS_PUBLIC__
|
||||
enum netadrtype_t {
|
||||
NA_BAD = 0,
|
||||
NA_LOOPBACK,
|
||||
NA_BROADCAST,
|
||||
NA_IP
|
||||
};
|
||||
|
||||
struct netadr_t {
|
||||
netadrtype_t type;
|
||||
unsigned char ip[4];
|
||||
unsigned short port;
|
||||
};
|
||||
#endif
|
||||
|
||||
class idSimpleSerializer {
|
||||
public:
|
||||
idSimpleSerializer(unsigned char* buffer, int bufferSize, bool write)
|
||||
: data(buffer), size(bufferSize), pos(0), writing(write) {
|
||||
}
|
||||
|
||||
bool Serialize(unsigned char& value);
|
||||
bool Serialize(unsigned int& value);
|
||||
bool SerializeBytes(char* bytes, unsigned int& numBytes);
|
||||
bool SerializeString(char* text, int maxSize);
|
||||
|
||||
int GetPos() const { return pos; }
|
||||
int GetSize() const { return size; }
|
||||
int GetSerializedSize() const { return writing ? pos : size; }
|
||||
bool IsWriting() const { return writing; }
|
||||
|
||||
private:
|
||||
unsigned char* data;
|
||||
int size;
|
||||
int pos;
|
||||
bool writing;
|
||||
};
|
||||
|
||||
#ifndef __SYS_PUBLIC__
|
||||
class idUDP {
|
||||
public:
|
||||
idUDP();
|
||||
virtual ~idUDP();
|
||||
|
||||
bool InitForPort(int portNumber, bool useBackend = false);
|
||||
void Close();
|
||||
bool GetPacket(netadr_t& from, void* data, int& size, int maxSize);
|
||||
bool GetPacketBlocking(netadr_t& from, void* data, int& size, int maxSize,
|
||||
int timeoutMS);
|
||||
void SendPacket(netadr_t to, const void* data, int size);
|
||||
|
||||
int GetPort() const { return bound_to.port; }
|
||||
netadr_t GetAdr() const { return bound_to; }
|
||||
bool IsOpen() const { return netSocket != 0; }
|
||||
void SetSilent(bool value) { silent = value; }
|
||||
bool GetSilent() const { return silent; }
|
||||
|
||||
int packetsRead;
|
||||
int bytesRead;
|
||||
int packetsWritten;
|
||||
int bytesWritten;
|
||||
|
||||
private:
|
||||
netadr_t bound_to;
|
||||
int netSocket;
|
||||
bool silent;
|
||||
};
|
||||
#endif
|
||||
|
||||
class idTCP {
|
||||
public:
|
||||
idTCP();
|
||||
virtual ~idTCP();
|
||||
|
||||
bool Connect(const char* host, unsigned short port, bool nonBlocking = false,
|
||||
bool silent = false, bool nagle = true);
|
||||
bool Select(int timeoutMS);
|
||||
bool IsOpen() const;
|
||||
void Close();
|
||||
int Read(void* data, int size);
|
||||
int ReadBlocking(void* data, int size, int timeoutMS);
|
||||
int Write(const void* data, int size);
|
||||
int WriteBlocking(const void* data, int size, int timeoutMS);
|
||||
bool WriteDataBlock(const char* buffer, int size, int timeoutMS);
|
||||
int ReadDataBlock(char* buffer, int bufferSize, int timeoutMS);
|
||||
|
||||
netadr_t GetAddress() const { return address; }
|
||||
|
||||
private:
|
||||
netadr_t address;
|
||||
int fd;
|
||||
};
|
||||
|
||||
#ifndef __SYS_PUBLIC__
|
||||
void Sys_InitNetworking();
|
||||
void Sys_ShutdownNetworking();
|
||||
bool Sys_StringToNetAdr(const char* text, netadr_t* address, bool doDNSResolve);
|
||||
const char* Sys_NetAdrToString(const netadr_t& address);
|
||||
bool Sys_IsLANAddress(const netadr_t& address);
|
||||
bool Sys_CompareNetAdrBase(const netadr_t& left, const netadr_t& right);
|
||||
int Sys_GetLocalIPCount();
|
||||
const char* Sys_GetLocalIP(int index);
|
||||
#endif
|
||||
|
||||
#if INTPTR_MAX == INT32_MAX
|
||||
static_assert(sizeof(netadr_t) == 12, "Recovered netadr_t ABI changed");
|
||||
static_assert(sizeof(idSimpleSerializer) == 16,
|
||||
"Recovered idSimpleSerializer ABI changed");
|
||||
#ifndef __SYS_PUBLIC__
|
||||
static_assert(sizeof(idUDP) == 40, "Recovered idUDP ABI changed");
|
||||
#endif
|
||||
static_assert(sizeof(idTCP) == 20, "Recovered idTCP ABI changed");
|
||||
#endif
|
||||
@@ -0,0 +1,115 @@
|
||||
#include "sys_time.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
thread_local char timeString[128];
|
||||
|
||||
bool LocalTime(const std::time_t value, std::tm& output) {
|
||||
#if defined(_WIN32)
|
||||
return localtime_s(&output, &value) == 0;
|
||||
#else
|
||||
return localtime_r(&value, &output) != nullptr;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t Sys_CurrentSystemTime() {
|
||||
#if defined(_WIN32)
|
||||
FILETIME fileTime;
|
||||
GetSystemTimeAsFileTime(&fileTime);
|
||||
return (static_cast<std::uint64_t>(fileTime.dwHighDateTime) << 32)
|
||||
| static_cast<std::uint64_t>(fileTime.dwLowDateTime);
|
||||
#else
|
||||
constexpr std::uint64_t windowsEpochOffset = 116444736000000000ULL;
|
||||
const auto ticks = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()
|
||||
).count() / 100;
|
||||
return windowsEpochOffset + static_cast<std::uint64_t>(ticks);
|
||||
#endif
|
||||
}
|
||||
|
||||
char* Sys_TimeStampToLogFormat(const int timeStamp) {
|
||||
timeString[0] = '\0';
|
||||
if (timeStamp == -1) {
|
||||
return timeString;
|
||||
}
|
||||
const std::time_t source = static_cast<std::time_t>(timeStamp);
|
||||
std::tm local = {};
|
||||
if (LocalTime(source, local)) {
|
||||
std::snprintf(timeString, sizeof(timeString),
|
||||
"%d-%02d-%02dT%02d:%02d:%02dZ",
|
||||
local.tm_year + 1900, local.tm_mon + 1, local.tm_mday,
|
||||
local.tm_hour, local.tm_min, local.tm_sec);
|
||||
}
|
||||
return timeString;
|
||||
}
|
||||
|
||||
char* Sys_TimeStampToStr(const int timeStamp, const bool padded) {
|
||||
timeString[0] = '\0';
|
||||
if (timeStamp == -1) {
|
||||
return timeString;
|
||||
}
|
||||
const std::time_t source = static_cast<std::time_t>(timeStamp);
|
||||
std::tm local = {};
|
||||
if (!LocalTime(source, local)) {
|
||||
return timeString;
|
||||
}
|
||||
|
||||
int hour = local.tm_hour % 12;
|
||||
if (hour == 0) {
|
||||
hour = 12;
|
||||
}
|
||||
if (padded) {
|
||||
std::snprintf(timeString, sizeof(timeString),
|
||||
"%02d/%02d/%d %02d:%02d:%02d%s",
|
||||
local.tm_mon + 1, local.tm_mday, local.tm_year + 1900,
|
||||
hour, local.tm_min, local.tm_sec,
|
||||
local.tm_hour < 12 ? "am" : "pm");
|
||||
} else {
|
||||
std::snprintf(timeString, sizeof(timeString),
|
||||
"%d/%d/%d %d:%02d:%02d%s",
|
||||
local.tm_mon + 1, local.tm_mday, local.tm_year + 1900,
|
||||
hour, local.tm_min, local.tm_sec,
|
||||
local.tm_hour < 12 ? "am" : "pm");
|
||||
}
|
||||
return timeString;
|
||||
}
|
||||
|
||||
char* Sys_DateStr(const bool padded) {
|
||||
return Sys_TimeStampToStr(static_cast<int>(std::time(nullptr)), padded);
|
||||
}
|
||||
|
||||
idStr Sys_SecToStr(int seconds) {
|
||||
idStr result;
|
||||
if (seconds < 0) {
|
||||
seconds = 0;
|
||||
}
|
||||
char buffer[32];
|
||||
const int weeks = seconds / 604800;
|
||||
if (weeks > 0) {
|
||||
std::snprintf(buffer, sizeof(buffer), "%dw, ", weeks);
|
||||
result.Append(buffer);
|
||||
seconds %= 604800;
|
||||
}
|
||||
const int days = seconds / 86400;
|
||||
if (weeks > 0 || days > 0) {
|
||||
std::snprintf(buffer, sizeof(buffer), "%dd, ", days);
|
||||
result.Append(buffer);
|
||||
seconds %= 86400;
|
||||
}
|
||||
const int hours = seconds / 3600;
|
||||
const int minutes = (seconds % 3600) / 60;
|
||||
const int remainingSeconds = seconds % 60;
|
||||
std::snprintf(buffer, sizeof(buffer), "%d:%02d:%02d",
|
||||
hours, minutes, remainingSeconds);
|
||||
result.Append(buffer);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "../text/str.h"
|
||||
|
||||
std::uint64_t Sys_CurrentSystemTime();
|
||||
char* Sys_TimeStampToLogFormat(int timeStamp);
|
||||
char* Sys_TimeStampToStr(int timeStamp, bool padded);
|
||||
char* Sys_DateStr(bool padded);
|
||||
idStr Sys_SecToStr(int seconds);
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
const char* Sys_GetOSUserName();
|
||||
const char* Sys_GetMachineName();
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#include "win_fibers.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
char* CopyFiberName(const char* source) {
|
||||
const char* const text = source == nullptr ? "" : source;
|
||||
const std::size_t length = std::strlen(text);
|
||||
char* const copy = static_cast<char*>(std::malloc(length + 1));
|
||||
if (copy != nullptr) {
|
||||
std::memcpy(copy, text, length + 1);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
idSysFiber::idSysFiber(const char* fiberName)
|
||||
: name(CopyFiberName(fiberName))
|
||||
, alive(true)
|
||||
, fiber(nullptr)
|
||||
, parent(nullptr) {
|
||||
fiber = CreateFiber(0x20000u, &idSysFiber::FiberRoutine, this);
|
||||
if (fiber == nullptr) {
|
||||
alive = false;
|
||||
}
|
||||
}
|
||||
|
||||
idSysFiber::~idSysFiber() {
|
||||
std::free(name);
|
||||
if (fiber != nullptr) {
|
||||
DeleteFiber(fiber);
|
||||
}
|
||||
}
|
||||
|
||||
bool idSysFiber::Execute() {
|
||||
if (alive && fiber != nullptr) {
|
||||
parent = GetCurrentFiber();
|
||||
SwitchToFiber(fiber);
|
||||
}
|
||||
return alive;
|
||||
}
|
||||
|
||||
void idSysFiber::YieldFiber() {
|
||||
if (parent != nullptr) {
|
||||
SwitchToFiber(parent);
|
||||
}
|
||||
}
|
||||
|
||||
void WINAPI idSysFiber::FiberRoutine(void* data) {
|
||||
idSysFiber* const self = static_cast<idSysFiber*>(data);
|
||||
self->Run();
|
||||
self->alive = false;
|
||||
self->YieldFiber();
|
||||
|
||||
// A finished Windows fiber must never return from its entry routine when
|
||||
// its parent is expected to retain control. Match the recovered guard.
|
||||
for (;;) {
|
||||
self->YieldFiber();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
class idSysFiber {
|
||||
public:
|
||||
explicit idSysFiber(const char* name);
|
||||
virtual ~idSysFiber();
|
||||
|
||||
idSysFiber(const idSysFiber&) = delete;
|
||||
idSysFiber& operator=(const idSysFiber&) = delete;
|
||||
|
||||
bool Execute();
|
||||
virtual void Run() = 0;
|
||||
|
||||
protected:
|
||||
void YieldFiber();
|
||||
|
||||
private:
|
||||
static void WINAPI FiberRoutine(void* data);
|
||||
|
||||
char* name;
|
||||
bool alive;
|
||||
void* fiber;
|
||||
void* parent;
|
||||
};
|
||||
|
||||
static_assert(sizeof(idSysFiber) == 20, "Recovered idSysFiber ABI changed");
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "../sys_alloc.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <Windows.h>
|
||||
|
||||
bool Sys_AllocWillUseMapHeap() {
|
||||
return !mem.IsGlobalHeap();
|
||||
}
|
||||
|
||||
void Sys_ReportHeaps() {
|
||||
std::printf("PC logical heap: %s, tracked bytes: %d\n",
|
||||
mem.IsGlobalHeap() ? "global" : "map", mem.BytesCurrentlyAllocated());
|
||||
}
|
||||
|
||||
void ReportGlobalMemoryStatus() {
|
||||
MEMORYSTATUSEX status = {};
|
||||
status.dwLength = sizeof(status);
|
||||
if (GlobalMemoryStatusEx(&status)) {
|
||||
std::printf("physical memory: %llu / %llu bytes available\n",
|
||||
static_cast<unsigned long long>(status.ullAvailPhys),
|
||||
static_cast<unsigned long long>(status.ullTotalPhys));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
#include "idlib/sys/sys_alloc.h"
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <psapi.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
namespace {
|
||||
|
||||
unsigned int ClampToUInt64(const unsigned long long value) {
|
||||
return value > (std::numeric_limits<unsigned int>::max)()
|
||||
? (std::numeric_limits<unsigned int>::max)()
|
||||
: static_cast<unsigned int>(value);
|
||||
}
|
||||
|
||||
int AlignUp(const int value, const int alignment) {
|
||||
const int safeAlignment = alignment <= 0 ? 16 : alignment;
|
||||
return (value + safeAlignment - 1) & ~(safeAlignment - 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void MapVirtualAddressSpace() {
|
||||
// Win32 already supplies a flat virtual address space. Logical map/system
|
||||
// heap selection remains handled by idMem.
|
||||
}
|
||||
|
||||
unsigned int Sys_GetStreamFileCacheUsage() {
|
||||
// The PC filesystem owns its cache allocations through idMem rather than
|
||||
// a carved physical-memory range.
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int Sys_GetMemoryUsage() {
|
||||
PROCESS_MEMORY_COUNTERS_EX counters = {};
|
||||
counters.cb = sizeof(counters);
|
||||
if (!GetProcessMemoryInfo(GetCurrentProcess(),
|
||||
reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&counters),
|
||||
sizeof(counters))) {
|
||||
return static_cast<unsigned int>(mem.BytesCurrentlyAllocated());
|
||||
}
|
||||
return ClampToUInt64(counters.PrivateUsage);
|
||||
}
|
||||
|
||||
unsigned int Sys_GetFreeMemory() {
|
||||
MEMORYSTATUSEX status = {};
|
||||
status.dwLength = sizeof(status);
|
||||
return GlobalMemoryStatusEx(&status) ? ClampToUInt64(status.ullAvailPhys) : 0;
|
||||
}
|
||||
|
||||
void Sys_WriteMemoryReport(const char* mapName, const char* version) {
|
||||
char fileName[192] = {};
|
||||
std::snprintf(fileName, sizeof(fileName), "memory_%s_%s.txt",
|
||||
mapName == nullptr || mapName[0] == '\0' ? "nomap" : mapName,
|
||||
version == nullptr || version[0] == '\0' ? "unknown" : version);
|
||||
for (char* cursor = fileName; *cursor != '\0'; ++cursor) {
|
||||
if (*cursor == '\\' || *cursor == '/' || *cursor == ':'
|
||||
|| *cursor == '*' || *cursor == '?' || *cursor == '"'
|
||||
|| *cursor == '<' || *cursor == '>' || *cursor == '|') {
|
||||
*cursor = '_';
|
||||
}
|
||||
}
|
||||
mem.WriteMemoryReport(".", fileName);
|
||||
}
|
||||
|
||||
void Sys_DumpMemory() {
|
||||
mem.WriteMemoryReport(".", "memory_dump.txt");
|
||||
}
|
||||
|
||||
void AddTagStats(int, int, int, int) {
|
||||
// idMem records tags per allocation, so a second accounting table would
|
||||
// double count on PC.
|
||||
}
|
||||
|
||||
void SubtractTagStats(int, int, int, int) {
|
||||
}
|
||||
|
||||
void* XMemAlloc(const unsigned int size, const int) {
|
||||
return mem.AllocWithLocation("XMemAlloc PC replacement", size, TAG_IDLIB,
|
||||
false, ALIGN_16, HEAP_DEFAULTHEAP);
|
||||
}
|
||||
|
||||
void XMemFree(unsigned char* pointer, const unsigned int) {
|
||||
mem.Free(pointer);
|
||||
}
|
||||
|
||||
void* Sys_Alloc(const unsigned int size, const memTag_t tag,
|
||||
const align_t alignment, const heapType_t heap) {
|
||||
return mem.AllocWithLocation("Sys_Alloc PC replacement", size, tag,
|
||||
false, alignment, heap);
|
||||
}
|
||||
|
||||
void Sys_Free(void* pointer) {
|
||||
mem.Free(pointer);
|
||||
}
|
||||
|
||||
idPhysicalMemoryBlock::idPhysicalMemoryBlock()
|
||||
: reservedPhysicalMemoryBlock(nullptr), totalBlockSize(0), commonBytes(0),
|
||||
overlayBytes(0), cacheBytes(0), insideResourceBlockLoad(false),
|
||||
physicalBytesAllocated(0), imageBytesAllocated(0), bufferBytesAllocated(0),
|
||||
otherBytesAllocated(0), alignmentWaste(0), bytesForcedOutsideBlock(0) {
|
||||
}
|
||||
|
||||
void idPhysicalMemoryBlock::Init(const int bytesToAllocate) {
|
||||
if (reservedPhysicalMemoryBlock != nullptr || bytesToAllocate <= 0) return;
|
||||
totalBlockSize = AlignUp(bytesToAllocate, 65536);
|
||||
reservedPhysicalMemoryBlock = static_cast<unsigned char*>(
|
||||
mem.AllocWithLocation("idPhysicalMemoryBlock PC reserve",
|
||||
totalBlockSize, TAG_PHYSICAL_BLOCK, true, ALIGN_16,
|
||||
HEAP_SYSTEMHEAP));
|
||||
if (reservedPhysicalMemoryBlock == nullptr) totalBlockSize = 0;
|
||||
}
|
||||
|
||||
void idPhysicalMemoryBlock::RevertToDiscreteAllocations() {
|
||||
if (physicalBytesAllocated != 0) return;
|
||||
mem.Free(reservedPhysicalMemoryBlock);
|
||||
reservedPhysicalMemoryBlock = nullptr;
|
||||
totalBlockSize = 0;
|
||||
commonBytes = overlayBytes = cacheBytes = 0;
|
||||
}
|
||||
|
||||
void idPhysicalMemoryBlock::BeginResourceLoads() {
|
||||
insideResourceBlockLoad = true;
|
||||
}
|
||||
|
||||
void idPhysicalMemoryBlock::EndResourceLoads(const bool neverFreeAllocatedData) {
|
||||
insideResourceBlockLoad = false;
|
||||
physicalBytesAllocated = std::min(AlignUp(physicalBytesAllocated, 65536),
|
||||
totalBlockSize);
|
||||
if (neverFreeAllocatedData) commonBytes = physicalBytesAllocated;
|
||||
cacheBytes = std::max(0, totalBlockSize - physicalBytesAllocated);
|
||||
overlayBytes = 0;
|
||||
}
|
||||
|
||||
void* idPhysicalMemoryBlock::PhysicalAlloc(const unsigned int bytes,
|
||||
const int alignment, const memTag_t tag) {
|
||||
const int alignedOffset = AlignUp(physicalBytesAllocated, alignment);
|
||||
alignmentWaste += alignedOffset - physicalBytesAllocated;
|
||||
if (reservedPhysicalMemoryBlock != nullptr
|
||||
&& alignedOffset >= 0
|
||||
&& bytes <= static_cast<unsigned int>(totalBlockSize - alignedOffset)) {
|
||||
physicalBytesAllocated = alignedOffset + static_cast<int>(bytes);
|
||||
if (tag == TAG_DXIMAGE) imageBytesAllocated += bytes;
|
||||
else if (tag == TAG_DXBUFFER) bufferBytesAllocated += bytes;
|
||||
else otherBytesAllocated += bytes;
|
||||
return reservedPhysicalMemoryBlock + alignedOffset;
|
||||
}
|
||||
bytesForcedOutsideBlock += bytes;
|
||||
return mem.AllocWithLocation("physical allocation fallback", bytes, tag,
|
||||
false, alignment >= ALIGN_128 ? ALIGN_128 : ALIGN_16,
|
||||
HEAP_SYSTEMHEAP);
|
||||
}
|
||||
|
||||
void* idPhysicalMemoryBlock::OverlayAlloc(const unsigned int bytes,
|
||||
const char*) {
|
||||
// PC resources are individually reclaimable; keeping overlays discrete
|
||||
// avoids the Xenon-only 64 KiB overlay fragmentation rules.
|
||||
return mem.AllocWithLocation("overlay allocation PC replacement", bytes,
|
||||
TAG_PHYSICAL_BLOCK, false, ALIGN_16, HEAP_SYSTEMHEAP);
|
||||
}
|
||||
|
||||
void idPhysicalMemoryBlock::OverlayFree(void* pointer) {
|
||||
if (!AddressIsInReservedPhysicalMemoryBlock(pointer)) mem.Free(pointer);
|
||||
}
|
||||
|
||||
bool idPhysicalMemoryBlock::AddressIsInReservedPhysicalMemoryBlock(
|
||||
const void* pointer) const {
|
||||
const unsigned char* const address = static_cast<const unsigned char*>(pointer);
|
||||
return reservedPhysicalMemoryBlock != nullptr
|
||||
&& address >= reservedPhysicalMemoryBlock
|
||||
&& address < reservedPhysicalMemoryBlock + totalBlockSize;
|
||||
}
|
||||
|
||||
bool idPhysicalMemoryBlock::AddressIsInOverlayPhysicalMemoryBlock(
|
||||
const void* pointer) const {
|
||||
if (!AddressIsInReservedPhysicalMemoryBlock(pointer) || overlayBytes <= 0) {
|
||||
return false;
|
||||
}
|
||||
const unsigned char* const overlayStart =
|
||||
reservedPhysicalMemoryBlock + totalBlockSize - overlayBytes;
|
||||
return static_cast<const unsigned char*>(pointer) >= overlayStart;
|
||||
}
|
||||
|
||||
void idPhysicalMemoryBlock::ReportPhysicalMemoryBlock() const {
|
||||
std::printf("physical block: %d/%d bytes, images=%d buffers=%d other=%d "
|
||||
"alignment=%d fallback=%d\n", physicalBytesAllocated, totalBlockSize,
|
||||
imageBytesAllocated, bufferBytesAllocated, otherBytesAllocated,
|
||||
alignmentWaste, bytesForcedOutsideBlock);
|
||||
}
|
||||
|
||||
void idPhysicalMemoryBlock::ReportUntouchedPhysicalMemory() const {
|
||||
std::printf("physical block untouched/available: %d bytes\n",
|
||||
std::max(0, totalBlockSize - physicalBytesAllocated));
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
#include "idlib/sys/sys_networking.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
std::mutex networkMutex;
|
||||
bool networkInitialized = false;
|
||||
std::vector<std::string> localAddresses;
|
||||
|
||||
SOCKET ToSocket(const int value) {
|
||||
return static_cast<SOCKET>(static_cast<unsigned int>(value));
|
||||
}
|
||||
|
||||
int FromSocket(const SOCKET value) {
|
||||
return static_cast<int>(value);
|
||||
}
|
||||
|
||||
void ClearAddress(netadr_t& address) {
|
||||
std::memset(&address, 0, sizeof(address));
|
||||
address.type = NA_BAD;
|
||||
}
|
||||
|
||||
void NetAdrToSockAdr(const netadr_t& source, sockaddr_in& destination) {
|
||||
std::memset(&destination, 0, sizeof(destination));
|
||||
destination.sin_family = AF_INET;
|
||||
if (source.type == NA_BROADCAST) {
|
||||
destination.sin_addr.s_addr = INADDR_BROADCAST;
|
||||
} else {
|
||||
std::memcpy(&destination.sin_addr.s_addr, source.ip, sizeof(source.ip));
|
||||
}
|
||||
destination.sin_port = htons(source.port);
|
||||
}
|
||||
|
||||
void SockAdrToNetAdr(const sockaddr_in& source, netadr_t& destination) {
|
||||
std::memset(&destination, 0, sizeof(destination));
|
||||
std::memcpy(destination.ip, &source.sin_addr.s_addr, sizeof(destination.ip));
|
||||
destination.port = ntohs(source.sin_port);
|
||||
destination.type = ntohl(source.sin_addr.s_addr) == INADDR_LOOPBACK
|
||||
? NA_LOOPBACK : NA_IP;
|
||||
}
|
||||
|
||||
bool WaitForSocket(const SOCKET socketValue, const int timeoutMS,
|
||||
const bool write) {
|
||||
if (socketValue == INVALID_SOCKET) return false;
|
||||
fd_set set;
|
||||
FD_ZERO(&set);
|
||||
FD_SET(socketValue, &set);
|
||||
timeval timeout = {};
|
||||
timeout.tv_sec = timeoutMS < 0 ? 0 : timeoutMS / 1000;
|
||||
timeout.tv_usec = timeoutMS < 0 ? 0 : (timeoutMS % 1000) * 1000;
|
||||
const int result = select(0, write ? nullptr : &set,
|
||||
write ? &set : nullptr, nullptr, timeoutMS < 0 ? nullptr : &timeout);
|
||||
return result > 0 && FD_ISSET(socketValue, &set) != 0;
|
||||
}
|
||||
|
||||
void EnsureNetworking() {
|
||||
std::lock_guard<std::mutex> guard(networkMutex);
|
||||
if (networkInitialized) return;
|
||||
WSADATA data = {};
|
||||
if (WSAStartup(MAKEWORD(2, 2), &data) != 0) {
|
||||
return;
|
||||
}
|
||||
networkInitialized = true;
|
||||
|
||||
localAddresses.clear();
|
||||
char hostName[256] = {};
|
||||
if (gethostname(hostName, sizeof(hostName)) == 0) {
|
||||
addrinfo hints = {};
|
||||
hints.ai_family = AF_INET;
|
||||
addrinfo* result = nullptr;
|
||||
if (getaddrinfo(hostName, nullptr, &hints, &result) == 0) {
|
||||
for (addrinfo* item = result; item != nullptr; item = item->ai_next) {
|
||||
const sockaddr_in* address =
|
||||
reinterpret_cast<const sockaddr_in*>(item->ai_addr);
|
||||
char text[INET_ADDRSTRLEN] = {};
|
||||
if (inet_ntop(AF_INET, &address->sin_addr, text, sizeof(text))
|
||||
!= nullptr
|
||||
&& std::find(localAddresses.begin(), localAddresses.end(), text)
|
||||
== localAddresses.end()) {
|
||||
localAddresses.emplace_back(text);
|
||||
}
|
||||
}
|
||||
freeaddrinfo(result);
|
||||
}
|
||||
}
|
||||
if (localAddresses.empty()) localAddresses.emplace_back("127.0.0.1");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool idSimpleSerializer::Serialize(unsigned char& value) {
|
||||
if (data == nullptr || pos < 0 || pos + 1 > size) return false;
|
||||
if (writing) data[pos] = value;
|
||||
else value = data[pos];
|
||||
++pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool idSimpleSerializer::Serialize(unsigned int& value) {
|
||||
if (data == nullptr || pos < 0 || pos + 4 > size) return false;
|
||||
if (writing) {
|
||||
data[pos + 0] = static_cast<unsigned char>(value >> 0);
|
||||
data[pos + 1] = static_cast<unsigned char>(value >> 8);
|
||||
data[pos + 2] = static_cast<unsigned char>(value >> 16);
|
||||
data[pos + 3] = static_cast<unsigned char>(value >> 24);
|
||||
} else {
|
||||
value = static_cast<unsigned int>(data[pos + 0])
|
||||
| (static_cast<unsigned int>(data[pos + 1]) << 8)
|
||||
| (static_cast<unsigned int>(data[pos + 2]) << 16)
|
||||
| (static_cast<unsigned int>(data[pos + 3]) << 24);
|
||||
}
|
||||
pos += 4;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool idSimpleSerializer::SerializeBytes(char* bytes, unsigned int& numBytes) {
|
||||
const unsigned int capacity = numBytes;
|
||||
if (!Serialize(numBytes)) return false;
|
||||
if (!writing && numBytes > capacity) {
|
||||
numBytes = 0;
|
||||
return false;
|
||||
}
|
||||
if (numBytes > static_cast<unsigned int>(size - pos)
|
||||
|| (numBytes > 0 && bytes == nullptr)) {
|
||||
return false;
|
||||
}
|
||||
if (writing) std::memcpy(data + pos, bytes, numBytes);
|
||||
else std::memcpy(bytes, data + pos, numBytes);
|
||||
pos += static_cast<int>(numBytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool idSimpleSerializer::SerializeString(char* text, const int maxSize) {
|
||||
if (text == nullptr || maxSize <= 0) return false;
|
||||
unsigned int bytes = writing
|
||||
? static_cast<unsigned int>(std::strlen(text))
|
||||
: static_cast<unsigned int>(maxSize - 1);
|
||||
if (!SerializeBytes(text, bytes)) return false;
|
||||
if (!writing) text[bytes] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
void Sys_InitNetworking() {
|
||||
EnsureNetworking();
|
||||
}
|
||||
|
||||
void Sys_ShutdownNetworking() {
|
||||
std::lock_guard<std::mutex> guard(networkMutex);
|
||||
if (!networkInitialized) return;
|
||||
localAddresses.clear();
|
||||
WSACleanup();
|
||||
networkInitialized = false;
|
||||
}
|
||||
|
||||
bool Sys_StringToNetAdr(const char* text, netadr_t* address,
|
||||
const bool doDNSResolve) {
|
||||
if (text == nullptr || address == nullptr) return false;
|
||||
EnsureNetworking();
|
||||
|
||||
std::string host(text);
|
||||
unsigned short port = 0;
|
||||
const std::size_t separator = host.rfind(':');
|
||||
if (separator != std::string::npos) {
|
||||
const long parsed = std::strtol(host.c_str() + separator + 1, nullptr, 10);
|
||||
if (parsed < 0 || parsed > 65535) return false;
|
||||
port = static_cast<unsigned short>(parsed);
|
||||
host.resize(separator);
|
||||
}
|
||||
if (host == "localhost") host = "127.0.0.1";
|
||||
|
||||
sockaddr_in socketAddress = {};
|
||||
socketAddress.sin_family = AF_INET;
|
||||
socketAddress.sin_port = htons(port);
|
||||
if (inet_pton(AF_INET, host.c_str(), &socketAddress.sin_addr) != 1) {
|
||||
if (!doDNSResolve) return false;
|
||||
addrinfo hints = {};
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
addrinfo* result = nullptr;
|
||||
if (getaddrinfo(host.c_str(), nullptr, &hints, &result) != 0
|
||||
|| result == nullptr) {
|
||||
if (result != nullptr) freeaddrinfo(result);
|
||||
return false;
|
||||
}
|
||||
socketAddress.sin_addr =
|
||||
reinterpret_cast<sockaddr_in*>(result->ai_addr)->sin_addr;
|
||||
freeaddrinfo(result);
|
||||
}
|
||||
SockAdrToNetAdr(socketAddress, *address);
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* Sys_NetAdrToString(const netadr_t& address) {
|
||||
thread_local char result[64];
|
||||
const char* prefix = address.type == NA_LOOPBACK ? "127.0.0.1" : nullptr;
|
||||
char ipText[INET_ADDRSTRLEN] = {};
|
||||
in_addr ipAddress = {};
|
||||
std::memcpy(&ipAddress.s_addr, address.ip, sizeof(address.ip));
|
||||
if (prefix == nullptr) {
|
||||
prefix = inet_ntop(AF_INET, &ipAddress, ipText, sizeof(ipText));
|
||||
}
|
||||
if (prefix == nullptr) prefix = "0.0.0.0";
|
||||
if (address.port != 0) {
|
||||
std::snprintf(result, sizeof(result), "%s:%u", prefix, address.port);
|
||||
} else {
|
||||
std::snprintf(result, sizeof(result), "%s", prefix);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Sys_IsLANAddress(const netadr_t& address) {
|
||||
if (address.type == NA_LOOPBACK) return true;
|
||||
if (address.type != NA_IP) return false;
|
||||
return address.ip[0] == 10
|
||||
|| (address.ip[0] == 172 && address.ip[1] >= 16 && address.ip[1] <= 31)
|
||||
|| (address.ip[0] == 192 && address.ip[1] == 168)
|
||||
|| address.ip[0] == 127;
|
||||
}
|
||||
|
||||
bool Sys_CompareNetAdrBase(const netadr_t& left, const netadr_t& right) {
|
||||
if (left.type != right.type) return false;
|
||||
if (left.type == NA_LOOPBACK) return true;
|
||||
if (left.type != NA_IP && left.type != NA_BROADCAST) return false;
|
||||
return std::memcmp(left.ip, right.ip, sizeof(left.ip)) == 0;
|
||||
}
|
||||
|
||||
int Sys_GetLocalIPCount() {
|
||||
EnsureNetworking();
|
||||
return static_cast<int>(localAddresses.size());
|
||||
}
|
||||
|
||||
const char* Sys_GetLocalIP(const int index) {
|
||||
EnsureNetworking();
|
||||
return index >= 0 && index < static_cast<int>(localAddresses.size())
|
||||
? localAddresses[index].c_str() : nullptr;
|
||||
}
|
||||
|
||||
idUDP::idUDP()
|
||||
: packetsRead(0), bytesRead(0), packetsWritten(0), bytesWritten(0),
|
||||
netSocket(0), silent(false) {
|
||||
ClearAddress(bound_to);
|
||||
}
|
||||
|
||||
idUDP::~idUDP() { Close(); }
|
||||
|
||||
bool idUDP::InitForPort(const int portNumber, const bool) {
|
||||
Close();
|
||||
EnsureNetworking();
|
||||
const SOCKET socketValue = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (socketValue == INVALID_SOCKET) return false;
|
||||
u_long nonBlocking = 1;
|
||||
ioctlsocket(socketValue, FIONBIO, &nonBlocking);
|
||||
BOOL broadcast = TRUE;
|
||||
setsockopt(socketValue, SOL_SOCKET, SO_BROADCAST,
|
||||
reinterpret_cast<const char*>(&broadcast), sizeof(broadcast));
|
||||
|
||||
sockaddr_in bindAddress = {};
|
||||
bindAddress.sin_family = AF_INET;
|
||||
bindAddress.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
bindAddress.sin_port = htons(static_cast<unsigned short>(portNumber));
|
||||
if (bind(socketValue, reinterpret_cast<sockaddr*>(&bindAddress),
|
||||
sizeof(bindAddress)) == SOCKET_ERROR) {
|
||||
closesocket(socketValue);
|
||||
return false;
|
||||
}
|
||||
int addressLength = sizeof(bindAddress);
|
||||
getsockname(socketValue, reinterpret_cast<sockaddr*>(&bindAddress), &addressLength);
|
||||
SockAdrToNetAdr(bindAddress, bound_to);
|
||||
netSocket = FromSocket(socketValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
void idUDP::Close() {
|
||||
if (netSocket != 0) closesocket(ToSocket(netSocket));
|
||||
netSocket = 0;
|
||||
ClearAddress(bound_to);
|
||||
}
|
||||
|
||||
bool idUDP::GetPacket(netadr_t& from, void* data, int& dataSize,
|
||||
const int maxSize) {
|
||||
if (!IsOpen() || data == nullptr || maxSize <= 0) return false;
|
||||
sockaddr_in source = {};
|
||||
int sourceLength = sizeof(source);
|
||||
const int received = recvfrom(ToSocket(netSocket), static_cast<char*>(data),
|
||||
maxSize, 0, reinterpret_cast<sockaddr*>(&source), &sourceLength);
|
||||
if (received == SOCKET_ERROR) return false;
|
||||
SockAdrToNetAdr(source, from);
|
||||
dataSize = received;
|
||||
++packetsRead;
|
||||
bytesRead += received;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool idUDP::GetPacketBlocking(netadr_t& from, void* data, int& dataSize,
|
||||
const int maxSize, const int timeoutMS) {
|
||||
return WaitForSocket(ToSocket(netSocket), timeoutMS, false)
|
||||
&& GetPacket(from, data, dataSize, maxSize);
|
||||
}
|
||||
|
||||
void idUDP::SendPacket(const netadr_t to, const void* data, const int dataSize) {
|
||||
if (!IsOpen() || data == nullptr || dataSize < 0 || to.type == NA_BAD) return;
|
||||
sockaddr_in destination = {};
|
||||
NetAdrToSockAdr(to, destination);
|
||||
const int sent = sendto(ToSocket(netSocket), static_cast<const char*>(data),
|
||||
dataSize, 0, reinterpret_cast<const sockaddr*>(&destination),
|
||||
sizeof(destination));
|
||||
if (sent >= 0) {
|
||||
++packetsWritten;
|
||||
bytesWritten += sent;
|
||||
}
|
||||
}
|
||||
|
||||
idTCP::idTCP() : fd(0) { ClearAddress(address); }
|
||||
idTCP::~idTCP() { Close(); }
|
||||
|
||||
bool idTCP::Connect(const char* host, const unsigned short port,
|
||||
const bool nonBlocking, const bool, const bool nagle) {
|
||||
Close();
|
||||
EnsureNetworking();
|
||||
if (!Sys_StringToNetAdr(host, &address, true)) return false;
|
||||
if (address.port == 0) address.port = port;
|
||||
address.type = address.type == NA_LOOPBACK ? NA_LOOPBACK : NA_IP;
|
||||
sockaddr_in destination = {};
|
||||
NetAdrToSockAdr(address, destination);
|
||||
const SOCKET socketValue = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (socketValue == INVALID_SOCKET) return false;
|
||||
BOOL noDelay = nagle ? FALSE : TRUE;
|
||||
setsockopt(socketValue, IPPROTO_TCP, TCP_NODELAY,
|
||||
reinterpret_cast<const char*>(&noDelay), sizeof(noDelay));
|
||||
if (nonBlocking) {
|
||||
u_long mode = 1;
|
||||
ioctlsocket(socketValue, FIONBIO, &mode);
|
||||
}
|
||||
const int result = connect(socketValue,
|
||||
reinterpret_cast<const sockaddr*>(&destination), sizeof(destination));
|
||||
if (result == SOCKET_ERROR) {
|
||||
const int error = WSAGetLastError();
|
||||
if (!nonBlocking || (error != WSAEWOULDBLOCK
|
||||
&& error != WSAEINPROGRESS && error != WSAEALREADY)) {
|
||||
closesocket(socketValue);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
fd = FromSocket(socketValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool idTCP::Select(const int timeoutMS) {
|
||||
return WaitForSocket(ToSocket(fd), timeoutMS, false);
|
||||
}
|
||||
|
||||
bool idTCP::IsOpen() const { return fd != 0; }
|
||||
|
||||
void idTCP::Close() {
|
||||
if (fd != 0) closesocket(ToSocket(fd));
|
||||
fd = 0;
|
||||
}
|
||||
|
||||
int idTCP::Read(void* data, const int dataSize) {
|
||||
if (!IsOpen() || data == nullptr || dataSize < 0) return -1;
|
||||
const int result = recv(ToSocket(fd), static_cast<char*>(data), dataSize, 0);
|
||||
if (result > 0) return result;
|
||||
if (result == 0) {
|
||||
Close();
|
||||
return -2;
|
||||
}
|
||||
if (WSAGetLastError() == WSAEWOULDBLOCK) return 0;
|
||||
Close();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int idTCP::ReadBlocking(void* data, const int dataSize, const int timeoutMS) {
|
||||
int total = 0;
|
||||
while (total < dataSize && WaitForSocket(ToSocket(fd), timeoutMS, false)) {
|
||||
const int received = Read(static_cast<char*>(data) + total, dataSize - total);
|
||||
if (received < 0) return received;
|
||||
total += received;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
int idTCP::Write(const void* data, const int dataSize) {
|
||||
if (!IsOpen() || data == nullptr || dataSize < 0) return -1;
|
||||
const int result = send(ToSocket(fd), static_cast<const char*>(data), dataSize, 0);
|
||||
if (result >= 0) return result;
|
||||
if (WSAGetLastError() == WSAEWOULDBLOCK) return 0;
|
||||
Close();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int idTCP::WriteBlocking(const void* data, const int dataSize, const int timeoutMS) {
|
||||
int total = 0;
|
||||
while (total < dataSize && WaitForSocket(ToSocket(fd), timeoutMS, true)) {
|
||||
const int sent = Write(static_cast<const char*>(data) + total, dataSize - total);
|
||||
if (sent < 0) return sent;
|
||||
total += sent;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
bool idTCP::WriteDataBlock(const char* buffer, const int dataSize,
|
||||
const int timeoutMS) {
|
||||
unsigned char lengthData[4] = {};
|
||||
unsigned int length = static_cast<unsigned int>(dataSize);
|
||||
idSimpleSerializer serializer(lengthData, sizeof(lengthData), true);
|
||||
return serializer.Serialize(length)
|
||||
&& WriteBlocking(lengthData, sizeof(lengthData), timeoutMS)
|
||||
== sizeof(lengthData)
|
||||
&& WriteBlocking(buffer, dataSize, timeoutMS) == dataSize;
|
||||
}
|
||||
|
||||
int idTCP::ReadDataBlock(char* buffer, const int bufferSize,
|
||||
const int timeoutMS) {
|
||||
unsigned char lengthData[4] = {};
|
||||
if (ReadBlocking(lengthData, sizeof(lengthData), timeoutMS)
|
||||
!= sizeof(lengthData)) return -1;
|
||||
unsigned int length = 0;
|
||||
idSimpleSerializer serializer(lengthData, sizeof(lengthData), false);
|
||||
if (!serializer.Serialize(length) || length > static_cast<unsigned int>(bufferSize)) {
|
||||
return -1;
|
||||
}
|
||||
return ReadBlocking(buffer, static_cast<int>(length), timeoutMS)
|
||||
== static_cast<int>(length) ? static_cast<int>(length) : -1;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "idlib/sys/sys_utils.h"
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
const char* Sys_GetOSUserName() {
|
||||
static char userName[64] = {};
|
||||
DWORD length = static_cast<DWORD>(sizeof(userName));
|
||||
if (!GetUserNameA(userName, &length)) {
|
||||
userName[0] = '\0';
|
||||
}
|
||||
return userName;
|
||||
}
|
||||
|
||||
const char* Sys_GetMachineName() {
|
||||
static char machineName[64] = {};
|
||||
DWORD length = static_cast<DWORD>(sizeof(machineName));
|
||||
if (!GetComputerNameA(machineName, &length)) {
|
||||
machineName[0] = '\0';
|
||||
}
|
||||
return machineName;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user