mirror of
https://github.com/OpenTTD/OpenTTD.git
synced 2025-03-06 06:15:04 +00:00
(svn r10391) -Add: debug support - structured dump of variables/classes/structures (with anti-recursion repository) into string
This commit is contained in:
parent
622235d8cd
commit
a88a1b6f53
@ -1181,6 +1181,12 @@
|
|||||||
<File
|
<File
|
||||||
RelativePath=".\..\src\misc\crc32.hpp">
|
RelativePath=".\..\src\misc\crc32.hpp">
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath=".\..\src\misc\dbg_helpers.cpp">
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath=".\..\src\misc\dbg_helpers.h">
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath=".\..\src\misc\fixedsizearray.hpp">
|
RelativePath=".\..\src\misc\fixedsizearray.hpp">
|
||||||
</File>
|
</File>
|
||||||
|
@ -1791,6 +1791,14 @@
|
|||||||
RelativePath=".\..\src\misc\crc32.hpp"
|
RelativePath=".\..\src\misc\crc32.hpp"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath=".\..\src\misc\dbg_helpers.cpp"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath=".\..\src\misc\dbg_helpers.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath=".\..\src\misc\fixedsizearray.hpp"
|
RelativePath=".\..\src\misc\fixedsizearray.hpp"
|
||||||
>
|
>
|
||||||
|
@ -369,6 +369,8 @@ misc/binaryheap.hpp
|
|||||||
misc/blob.hpp
|
misc/blob.hpp
|
||||||
misc/countedptr.hpp
|
misc/countedptr.hpp
|
||||||
misc/crc32.hpp
|
misc/crc32.hpp
|
||||||
|
misc/dbg_helpers.cpp
|
||||||
|
misc/dbg_helpers.h
|
||||||
misc/fixedsizearray.hpp
|
misc/fixedsizearray.hpp
|
||||||
misc/hashtable.hpp
|
misc/hashtable.hpp
|
||||||
misc/str.hpp
|
misc/str.hpp
|
||||||
|
170
src/misc/dbg_helpers.cpp
Normal file
170
src/misc/dbg_helpers.cpp
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
/* $Id:$ */
|
||||||
|
|
||||||
|
/** @file dbg_helpers.cpp */
|
||||||
|
#include "../stdafx.h"
|
||||||
|
#include "../direction.h"
|
||||||
|
#include "../rail.h"
|
||||||
|
#include "../rail_map.h"
|
||||||
|
#include "dbg_helpers.h"
|
||||||
|
|
||||||
|
/** Trackdir & TrackdirBits short names. */
|
||||||
|
static const char* trackdir_names[] = {
|
||||||
|
"NE", "SE", "UE", "LE", "LS", "RS", "rne", "rse",
|
||||||
|
"SW", "NW", "UW", "LW", "LN", "RN", "rsw", "rnw",
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Return name of given Trackdir. */
|
||||||
|
CStrA ValueStr(Trackdir td)
|
||||||
|
{
|
||||||
|
CStrA out;
|
||||||
|
out.Format("%d (%s)", td, ItemAtT(td, trackdir_names, "UNK", INVALID_TRACKDIR, "INV"));
|
||||||
|
return out.Transfer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return composed name of given TrackdirBits. */
|
||||||
|
CStrA ValueStr(TrackdirBits td_bits)
|
||||||
|
{
|
||||||
|
CStrA out;
|
||||||
|
out.Format("%d (%s)", td_bits, ComposeNameT(td_bits, trackdir_names, "UNK", INVALID_TRACKDIR_BIT, "INV").Data());
|
||||||
|
return out.Transfer();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** DiagDirection short names. */
|
||||||
|
static const char* diagdir_names[] = {
|
||||||
|
"NE", "SE", "SW", "NW",
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Return name of given DiagDirection. */
|
||||||
|
CStrA ValueStr(DiagDirection dd)
|
||||||
|
{
|
||||||
|
CStrA out;
|
||||||
|
out.Format("%d (%s)", dd, ItemAtT(dd, diagdir_names, "UNK", INVALID_DIAGDIR, "INV"));
|
||||||
|
return out.Transfer();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** SignalType short names. */
|
||||||
|
static const char* signal_type_names[] = {
|
||||||
|
"NORMAL", "ENTRY", "EXIT", "COMBO",
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Return name of given SignalType. */
|
||||||
|
CStrA ValueStr(SignalType t)
|
||||||
|
{
|
||||||
|
CStrA out;
|
||||||
|
out.Format("%d (%s)", t, ItemAtT(t, signal_type_names, "UNK"));
|
||||||
|
return out.Transfer();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Translate TileIndex into string. */
|
||||||
|
CStrA TileStr(TileIndex tile)
|
||||||
|
{
|
||||||
|
CStrA out;
|
||||||
|
out.Format("0x%04X (%d, %d)", tile, TileX(tile), TileY(tile));
|
||||||
|
return out.Transfer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Keep track of the last assigned type_id. Used for anti-recursion. */
|
||||||
|
/*static*/ size_t& DumpTarget::LastTypeId()
|
||||||
|
{
|
||||||
|
static size_t last_type_id = 0;
|
||||||
|
return last_type_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return structured name of the current class/structure. */
|
||||||
|
CStrA DumpTarget::GetCurrentStructName()
|
||||||
|
{
|
||||||
|
CStrA out;
|
||||||
|
if (!m_cur_struct.empty()) {
|
||||||
|
// we are inside some named struct, return its name
|
||||||
|
out = m_cur_struct.top();
|
||||||
|
}
|
||||||
|
return out.Transfer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the given instance in our anti-recursion repository.
|
||||||
|
* Return true and set name when object was found.
|
||||||
|
*/
|
||||||
|
bool DumpTarget::FindKnownName(size_t type_id, const void *ptr, CStrA &name)
|
||||||
|
{
|
||||||
|
KNOWN_NAMES::const_iterator it = m_known_names.find(KnownStructKey(type_id, ptr));
|
||||||
|
if (it != m_known_names.end()) {
|
||||||
|
/* we have found it */
|
||||||
|
name = (*it).second;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Write some leading spaces into the output. */
|
||||||
|
void DumpTarget::WriteIndent()
|
||||||
|
{
|
||||||
|
int num_spaces = 2 * m_indent;
|
||||||
|
memset(m_out.GrowSizeNC(num_spaces), ' ', num_spaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Write a line with indent at the beginning and <LF> at the end. */
|
||||||
|
void DumpTarget::WriteLine(const char *format, ...)
|
||||||
|
{
|
||||||
|
WriteIndent();
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
m_out.AddFormatL(format, args);
|
||||||
|
va_end(args);
|
||||||
|
m_out.AppendStr("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Write 'name = value' with indent and new-line. */
|
||||||
|
void DumpTarget::WriteValue(const char *name, const char *value_str)
|
||||||
|
{
|
||||||
|
WriteIndent();
|
||||||
|
m_out.AddFormat("%s = %s\n", name, value_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Write name & TileIndex to the output. */
|
||||||
|
void DumpTarget::WriteTile(const char *name, TileIndex tile)
|
||||||
|
{
|
||||||
|
WriteIndent();
|
||||||
|
m_out.AddFormat("%s = %s\n", name, TileStr(tile).Data());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open new structure (one level deeper than the current one) 'name = {<LF>'.
|
||||||
|
*/
|
||||||
|
void DumpTarget::BeginStruct(size_t type_id, const char *name, const void *ptr)
|
||||||
|
{
|
||||||
|
/* make composite name */
|
||||||
|
CStrA cur_name = GetCurrentStructName().Transfer();
|
||||||
|
if (cur_name.Size() > 0) {
|
||||||
|
/* add name delimiter (we use structured names) */
|
||||||
|
cur_name.AppendStr(".");
|
||||||
|
}
|
||||||
|
cur_name.AppendStr(name);
|
||||||
|
|
||||||
|
/* put the name onto stack (as current struct name) */
|
||||||
|
m_cur_struct.push(cur_name);
|
||||||
|
|
||||||
|
/* put it also to the map of known structures */
|
||||||
|
m_known_names.insert(KNOWN_NAMES::value_type(KnownStructKey(type_id, ptr), cur_name));
|
||||||
|
|
||||||
|
WriteIndent();
|
||||||
|
m_out.AddFormat("%s = {\n", name);
|
||||||
|
m_indent++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close structure '}<LF>'.
|
||||||
|
*/
|
||||||
|
void DumpTarget::EndStruct()
|
||||||
|
{
|
||||||
|
m_indent--;
|
||||||
|
WriteIndent();
|
||||||
|
m_out.AddFormat("}\n");
|
||||||
|
|
||||||
|
/* remove current struct name from the stack */
|
||||||
|
m_cur_struct.pop();
|
||||||
|
}
|
||||||
|
|
166
src/misc/dbg_helpers.h
Normal file
166
src/misc/dbg_helpers.h
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
/* $Id:$ */
|
||||||
|
|
||||||
|
/** @file dbg_helpers.h */
|
||||||
|
|
||||||
|
#ifndef DBG_HELPERS
|
||||||
|
#define DBG_HELPERS
|
||||||
|
|
||||||
|
#include <new>
|
||||||
|
#include <map>
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
|
#include "blob.hpp"
|
||||||
|
#include "str.hpp"
|
||||||
|
|
||||||
|
/** Helper template class that provides C array length and item type */
|
||||||
|
template <typename T> struct ArrayT;
|
||||||
|
|
||||||
|
/** Helper template class that provides C array length and item type */
|
||||||
|
template <typename T, size_t N> struct ArrayT<T[N]> {
|
||||||
|
static const size_t length = N;
|
||||||
|
typedef T item_t;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper template function that returns item of array at given index
|
||||||
|
* or t_unk when index is out of bounds.
|
||||||
|
*/
|
||||||
|
template <typename E, typename T>
|
||||||
|
inline typename ArrayT<T>::item_t ItemAtT(E idx, T &t, typename ArrayT<T>::item_t t_unk)
|
||||||
|
{
|
||||||
|
if ((size_t)idx >= ArrayT<T>::length) {
|
||||||
|
return t_unk;
|
||||||
|
}
|
||||||
|
return t[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper template function that returns item of array at given index
|
||||||
|
* or t_inv when index == idx_inv
|
||||||
|
* or t_unk when index is out of bounds.
|
||||||
|
*/
|
||||||
|
template <typename E, typename T>
|
||||||
|
inline typename ArrayT<T>::item_t ItemAtT(E idx, T &t, typename ArrayT<T>::item_t t_unk, E idx_inv, typename ArrayT<T>::item_t t_inv)
|
||||||
|
{
|
||||||
|
if ((size_t)idx < ArrayT<T>::length) {
|
||||||
|
return t[idx];
|
||||||
|
}
|
||||||
|
if (idx == idx_inv) {
|
||||||
|
return t_inv;
|
||||||
|
}
|
||||||
|
return t_unk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper template function that returns compound bitfield name that is
|
||||||
|
* concatenation of names of each set bit in the given value
|
||||||
|
* or t_inv when index == idx_inv
|
||||||
|
* or t_unk when index is out of bounds.
|
||||||
|
*/
|
||||||
|
template <typename E, typename T>
|
||||||
|
inline CStrA ComposeNameT(E value, T &t, const char* t_unk, E val_inv, const char* name_inv)
|
||||||
|
{
|
||||||
|
CStrA out;
|
||||||
|
if (value == val_inv) {
|
||||||
|
out = name_inv;
|
||||||
|
} else if (value == 0) {
|
||||||
|
out = "<none>";
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < ArrayT<T>::length; i++) {
|
||||||
|
if ((value & (1 << i)) == 0) continue;
|
||||||
|
out.AddFormat("%s%s", (out.Size() > 0 ? "+" : ""), t[i]);
|
||||||
|
value &= ~(E)(1 << i);
|
||||||
|
}
|
||||||
|
if (value != 0) out.AddFormat("%s%s", (out.Size() > 0 ? "+" : ""), t_unk);
|
||||||
|
}
|
||||||
|
return out.Transfer();
|
||||||
|
}
|
||||||
|
|
||||||
|
CStrA ValueStr(Trackdir td);
|
||||||
|
CStrA ValueStr(TrackdirBits td_bits);
|
||||||
|
CStrA ValueStr(DiagDirection dd);
|
||||||
|
CStrA ValueStr(SignalType t);
|
||||||
|
|
||||||
|
/** Class that represents the dump-into-string target. */
|
||||||
|
struct DumpTarget {
|
||||||
|
|
||||||
|
/** Used as a key into map of known object instances. */
|
||||||
|
struct KnownStructKey {
|
||||||
|
size_t m_type_id;
|
||||||
|
const void *m_ptr;
|
||||||
|
|
||||||
|
KnownStructKey(size_t type_id, const void *ptr)
|
||||||
|
: m_type_id(type_id)
|
||||||
|
, m_ptr(ptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
KnownStructKey(const KnownStructKey &src)
|
||||||
|
{
|
||||||
|
m_type_id = src.m_type_id;
|
||||||
|
m_ptr = src.m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator < (const KnownStructKey &other) const
|
||||||
|
{
|
||||||
|
if ((size_t)m_ptr < (size_t)other.m_ptr) return true;
|
||||||
|
if ((size_t)m_ptr > (size_t)other.m_ptr) return false;
|
||||||
|
if (m_type_id < other.m_type_id) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::map<KnownStructKey, CStrA> KNOWN_NAMES;
|
||||||
|
|
||||||
|
CStrA m_out; ///< the output string
|
||||||
|
int m_indent; ///< current indent/nesting level
|
||||||
|
std::stack<CStrA> m_cur_struct; ///< here we will track the current structure name
|
||||||
|
KNOWN_NAMES m_known_names; ///< map of known object instances and their structured names
|
||||||
|
|
||||||
|
DumpTarget()
|
||||||
|
: m_indent(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
static size_t& LastTypeId();
|
||||||
|
CStrA GetCurrentStructName();
|
||||||
|
bool FindKnownName(size_t type_id, const void* ptr, CStrA &name);
|
||||||
|
|
||||||
|
void WriteIndent();
|
||||||
|
|
||||||
|
void WriteLine(const char *format, ...);
|
||||||
|
void WriteValue(const char *name, const char *value_str);
|
||||||
|
void WriteTile(const char *name, TileIndex t);
|
||||||
|
|
||||||
|
/** Dump given enum value (as a number and as named value) */
|
||||||
|
template <typename E> void WriteEnumT(const char *name, E e)
|
||||||
|
{
|
||||||
|
WriteValue(name, ValueStr(e).Data());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BeginStruct(size_t type_id, const char *name, const void *ptr);
|
||||||
|
void EndStruct();
|
||||||
|
|
||||||
|
/** Dump nested object (or only its name if this instance is already known). */
|
||||||
|
template <typename S> void WriteStructT(const char *name, const S *s)
|
||||||
|
{
|
||||||
|
static size_t type_id = ++LastTypeId();
|
||||||
|
|
||||||
|
if (s == NULL) {
|
||||||
|
/* No need to dump NULL struct. */
|
||||||
|
WriteLine("%s = <null>", name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CStrA known_as;
|
||||||
|
if (FindKnownName(type_id, s, known_as)) {
|
||||||
|
/* We already know this one, no need to dump it. */
|
||||||
|
WriteLine("%s = known_as.%s", name, known_as.Data());
|
||||||
|
} else {
|
||||||
|
/* Still unknown, dump it */
|
||||||
|
BeginStruct(type_id, name, s);
|
||||||
|
s->Dump(*this);
|
||||||
|
EndStruct();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* DBG_HELPERS */
|
Loading…
Reference in New Issue
Block a user