Moved logging functions into OSD/Logger.cpp (eventually logging system will be reworked entirely) and added new config tree structure (not yet used).

This commit is contained in:
Bart Trzynadlowski 2016-07-05 01:15:58 +00:00
parent 0650ffc37c
commit bcc663d4eb
9 changed files with 724 additions and 62 deletions

View file

@ -119,7 +119,8 @@ endif
#
# Objects and Dependencies
#
OBJ = $(OBJ_DIR)/PPCDisasm.o $(OBJ_DIR)/Games.o $(OBJ_DIR)/Config.o $(OBJ_DIR)/INIFile.o $(OBJ_DIR)/BlockFile.o $(OBJ_DIR)/93C46.o \
OBJ = $(OBJ_DIR)/Format.o $(OBJ_DIR)/NewConfig.o \
$(OBJ_DIR)/PPCDisasm.o $(OBJ_DIR)/Games.o $(OBJ_DIR)/Config.o $(OBJ_DIR)/INIFile.o $(OBJ_DIR)/BlockFile.o $(OBJ_DIR)/93C46.o \
$(OBJ_DIR)/ROMLoad.o $(OBJ_DIR)/unzip.o $(OBJ_DIR)/ioapi.o $(OBJ_DIR)/Error.o $(OBJ_DIR)/glew.o $(OBJ_DIR)/Shader.o \
$(OBJ_DIR)/Real3D.o $(OBJ_DIR)/Legacy3D.o $(OBJ_DIR)/Models.o $(OBJ_DIR)/TextureRefs.o \
$(OBJ_DIR)/New3D.o $(OBJ_DIR)/Mat4.o $(OBJ_DIR)/Model.o $(OBJ_DIR)/PolyHeader.o $(OBJ_DIR)/Texture.o $(OBJ_DIR)/TextureSheet.o $(OBJ_DIR)/VBO.o $(OBJ_DIR)/Vec.o $(OBJ_DIR)/R3DShader.o $(OBJ_DIR)/R3DFloat.o \
@ -135,7 +136,7 @@ OBJ = $(OBJ_DIR)/PPCDisasm.o $(OBJ_DIR)/Games.o $(OBJ_DIR)/Config.o $(OBJ_DIR)/I
$(OBJ_DIR)/amp_layer2.o $(OBJ_DIR)/amp_layer3.o $(OBJ_DIR)/amp_misc2.o $(OBJ_DIR)/amp_position.o $(OBJ_DIR)/amp_transform.o \
$(OBJ_DIR)/amp_util.o \
$(OBJ_DIR)/Crypto.o \
$(OBJ_DIR)/Format.o
$(OBJ_DIR)/Logger.o
# If built-in debugger enabled, include all debugging classes
@ -157,6 +158,10 @@ ppcd: $(BIN_DIR) $(OBJ_DIR)
$(CXX) Src/CPU/PowerPC/PPCDisasm.cpp $(CPPFLAGS) -DSTANDALONE -o $(OBJ_DIR)/ppcd.o
$(LD) -o $(BIN_DIR)/ppcd.exe -mconsole $(OBJ_DIR)/ppcd.o
tests: $(BIN_DIR) $(OBJ_DIR) $(OBJ) Src/Util/Test_Config.cpp
$(CXX) Src/Util/Test_Config.cpp $(CPPFLAGS) -o $(OBJ_DIR)/Test_Config.o
$(LD) -o $(BIN_DIR)/test_config.exe -mconsole $(OBJ_DIR)/Test_Config.o $(OBJ_DIR)/Format.o $(OBJ_DIR)/NewConfig.o $(OBJ_DIR)/Logger.o
$(BIN_DIR):
mkdir $(BIN_DIR)

45
Src/OSD/Logger.cpp Normal file
View file

@ -0,0 +1,45 @@
#include "OSD/Logger.h"
// Logger object is used to redirect log messages appropriately
static CLogger *s_Logger = NULL;
CLogger *GetLogger()
{
return s_Logger;
}
void SetLogger(CLogger *Logger)
{
s_Logger = Logger;
}
void DebugLog(const char *fmt, ...)
{
if (s_Logger == NULL)
return;
va_list vl;
va_start(vl, fmt);
s_Logger->DebugLog(fmt, vl);
va_end(vl);
}
void InfoLog(const char *fmt, ...)
{
if (s_Logger == NULL)
return;
va_list vl;
va_start(vl, fmt);
s_Logger->InfoLog(fmt, vl);
va_end(vl);
}
bool ErrorLog(const char *fmt, ...)
{
if (s_Logger == NULL)
return FAIL;
va_list vl;
va_start(vl, fmt);
s_Logger->ErrorLog(fmt, vl);
va_end(vl);
return FAIL;
}

View file

@ -239,8 +239,8 @@ private:
/******************************************************************************
Log Functions
Message logging interface. All messages are passed by the OSD layer to the
currently active logger object.
Message logging interface. All messages are passed to the currently active
logger object.
******************************************************************************/
/*

View file

@ -63,59 +63,10 @@
#include "WinOutputs.h"
#endif
/******************************************************************************
Error and Debug Logging
******************************************************************************/
// Log file names
#define DEBUG_LOG_FILE "debug.log"
#define ERROR_LOG_FILE "error.log"
// Logger object is used to redirect log messages appropriately
static CLogger *s_Logger = NULL;
CLogger *GetLogger()
{
return s_Logger;
}
void SetLogger(CLogger *Logger)
{
s_Logger = Logger;
}
void DebugLog(const char *fmt, ...)
{
if (s_Logger == NULL)
return;
va_list vl;
va_start(vl, fmt);
s_Logger->DebugLog(fmt, vl);
va_end(vl);
}
void InfoLog(const char *fmt, ...)
{
if (s_Logger == NULL)
return;
va_list vl;
va_start(vl, fmt);
s_Logger->InfoLog(fmt, vl);
va_end(vl);
}
bool ErrorLog(const char *fmt, ...)
{
if (s_Logger == NULL)
return FAIL;
va_list vl;
va_start(vl, fmt);
s_Logger->ErrorLog(fmt, vl);
va_end(vl);
return FAIL;
}
/******************************************************************************
Display Management
@ -1549,9 +1500,9 @@ static void PrintGameList(void)
*
* Program entry point.
*/
int main(int argc, char **argv)
{
#ifdef SUPERMODEL_DEBUGGER
bool cmdEnterDebugger = false;
#endif // SUPERMODEL_DEBUGGER

View file

@ -1,10 +1,33 @@
#include "Util/Format.h"
#include <cctype>
namespace Util
{
std::string TrimWhiteSpace(const std::string &str)
{
if (str.empty())
return std::string();
size_t first = 0;
for (; first < str.length(); ++first)
{
if (!isspace(str[first]))
break;
}
if (first >= str.length())
return std::string();
size_t last = str.length() - 1;
for (; last > first; --last)
{
if (!isspace(str[last]))
break;
}
++last;
return std::string(str.c_str() + first, last - first);
}
static const char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
const std::string Hex(uint32_t n, size_t num_digits)
std::string Hex(uint32_t n, size_t num_digits)
{
Util::Format f;
f << "0x";
@ -16,17 +39,17 @@ namespace Util
return f;
}
const std::string Hex(uint32_t n)
std::string Hex(uint32_t n)
{
return Hex(n, 8);
}
const std::string Hex(uint16_t n)
std::string Hex(uint16_t n)
{
return Hex(n, 4);
}
const std::string Hex(uint8_t n)
std::string Hex(uint8_t n)
{
return Hex(n, 2);
}

View file

@ -84,10 +84,11 @@ namespace Util
}
};
const std::string Hex(uint32_t n, size_t num_digits);
const std::string Hex(uint32_t n);
const std::string Hex(uint16_t n);
const std::string Hex(uint8_t n);
std::string TrimWhiteSpace(const std::string &str);
std::string Hex(uint32_t n, size_t num_digits);
std::string Hex(uint32_t n);
std::string Hex(uint16_t n);
std::string Hex(uint8_t n);
} // Util
#endif // INCLUDED_FORMAT_H

372
Src/Util/NewConfig.cpp Normal file
View file

@ -0,0 +1,372 @@
/*
* Config Tree
* -----------
*
* A hierarchical data structure supporting arbitrary nesting. Each node
* (Config::Node) has a key and either a value or children (in fact, it may
* have both, but this does not make semantic sense and so the config tree
* builders take care not to allow it).
*
* All values are simply strings for now. They may have defaults, which are
* actually supplied by the caller and returned when a queried node cannot be
* found.
*
* Nodes at the same nesting level (siblings) are strung together in a
* linked list. Parents also maintain pointers to the first and last of their
* children (for order-preserving iteration) as well as a map for direct
* lookup by key.
*
* Keys may be reused at a given level. Key-value pairs will have their order
* preserved when iterated but only the most recent key is returned in direct
* map lookups.
*
* Example:
*
* <game name="scud">
* <roms>
* <crom name="foo.bin" offset=0/>
* <crom name="bar.bin" offset=2/>
* </roms>
*
* config["game/roms"].Value() <-- should return nothing
* config["game/roms/crom/offset"].Value() <-- "2", the second <crom> tag
* config["game/roms"].begin() <-- iterate over two <crom> tags
*
* INI Semantics
* -------------
*
* INI files are linear in nature and divided into sections. Config trees built
* to represent INI files must adhere to the following rules:
*
* - Section nodes have their key set to the section name and value empty.
* They are the only nodes that can have children (i.e., IsLeaf() == false,
* HasChildren() == true).
* - Top-level node in the tree is the global section, and its key is
* "Global".
* - Only the global section (top-level section) may have child nodes that are
* sections. The config tree can therefore only be up to 2 levels deep: the
* first, top-most level consists of global parameters, and section nodes
* provide one more level of nesting.
*
* INI files contain zero or more sections followed by zero or more settings
* each.
*
* Setting0 = 100
* [ Global ]
* Setting1 = 200
* [ Section1 ]
* SettingA = 1 ; this is a comment
* SettingB = foo
* [ Section2 ]
* SettingX = bar
* SettingZ = "baz" ; quotes are optional and are stripped off during parsing
*
* Any setting not explicitly part of a section is assigned to the "Global"
* section. For example, Setting0 and Setting1 above are both part of "Global".
*/
#include "Util/NewConfig.h"
#include "OSD/Logger.h"
#include <algorithm>
#include <fstream>
#include <iostream>
namespace Util
{
namespace Config
{
Node Node::s_empty_node;
const Node &Node::operator[](const std::string &path) const
{
const Node *e = this;
std::vector<std::string> keys = Util::Format(path).Split('/');
for (auto &key: keys)
{
auto it = e->m_children.find(key);
if (it == e->m_children.end())
return s_empty_node;
e = it->second.get();
}
return *e;
}
Node &Node::Get(const std::string &path)
{
return const_cast<Node &>(operator[](path));
}
const Node &Node::Get(const std::string &path) const
{
return operator[](path);
}
void Node::Print(size_t indent_level) const
{
std::fill_n(std::ostream_iterator<char>(std::cout), 2 * indent_level, ' ');
std::cout << m_key;
if (m_value.length())
std::cout << '=' << m_value;
std::cout << " children={";
for (auto v: m_children)
std::cout << ' ' << v.first;
std::cout << " }" << std::endl;
for (Ptr_t child = m_first_child; child; child = child->m_next_sibling)
child->Print(indent_level + 1);
}
Node &Node::Create(const std::string &key)
{
Ptr_t node = std::make_shared<Node>(key);
AddChild(node);
return *node;
}
Node &Node::Create(const std::string &key, const std::string &value)
{
Ptr_t node = std::make_shared<Node>(key, value);
AddChild(node);
return *node;
}
// Adds a newly-created node (which, among other things, implies no
// children) as a child
void Node::AddChild(Ptr_t &node)
{
if (!m_last_child)
{
m_first_child = node;
m_last_child = node;
}
else
{
m_last_child->m_next_sibling = node;
m_last_child = node;
}
m_children[node->m_key] = node;
}
/*
void Node::Clear()
{
m_next_sibling.reset();
m_first_child.reset();
m_last_child.reset();
m_children.clear();
m_key.clear();
m_value.clear();
}
*/
Node::Node()
{
//std::cout << "Created " << "<null>" << " (" << this << ")" << std::endl;
}
Node::Node(const std::string &key)
: m_key(key)
{
//std::cout << "Created " << key << " (" << this << ")" << std::endl;
}
Node::Node(const std::string &key, const std::string &value)
: m_key(key),
m_value(value)
{
//std::cout << "Created " << key << '=' << value << " (" << this << ")" << std::endl;
}
// Deep copy
Node::Node(const Node &that)
: m_key(that.m_key),
m_value(that.m_value)
{
for (Ptr_t child = that.m_first_child; child; child = child->m_next_sibling)
{
Ptr_t copied_child = std::make_shared<Node>(*child);
AddChild(copied_child);
}
}
Node::~Node()
{
//std::cout << "Destroyed " << m_key << " (" << this << ")" << std::endl;
}
Node::Ptr_t CreateEmpty()
{
return std::shared_ptr<Node>(new Node());
}
static std::string StripComment(const std::string &line)
{
// Find first semicolon not enclosed in ""
bool inside_quotes = false;
for (auto it = line.begin(); it != line.end(); ++it)
{
inside_quotes ^= (*it == '\"');
if (!inside_quotes && *it == ';')
return std::string(line.begin(), it);
}
return line;
}
static void ParseAssignment(Node &current_section, const std::string &filename, size_t line_num, const std::string &line)
{
size_t idx_equals = line.find_first_of('=');
if (idx_equals == std::string::npos)
{
//std::cerr << filename << ':' << line_num << ": Syntax error. No '=' found." << std::endl;
ErrorLog("%s:%d: Syntax error. No '=' found.", filename.c_str(), line_num);
return;
}
std::string lvalue(TrimWhiteSpace(std::string(line.begin(), line.begin() + idx_equals)));
if (lvalue.empty())
{
//std::cerr << filename << ':' << line_num << ": Syntax error. Setting name missing before '='." << std::endl;
ErrorLog("%s:%d: Syntax error. Setting name missing before '='.", filename.c_str(), line_num);
return;
}
// Get rvalue, which is allowed to be empty and strip off quotes if they
// exist. Only a single pair of quotes encasing the rvalue are permitted.
std::string rvalue(TrimWhiteSpace(std::string(line.begin() + idx_equals + 1, line.end())));
size_t idx_first_quote = rvalue.find_first_of('\"');
size_t idx_last_quote = rvalue.find_last_of('\"');
if (idx_first_quote == 0 && idx_last_quote == (rvalue.length() - 1) && idx_first_quote != idx_last_quote)
rvalue = std::string(rvalue.begin() + idx_first_quote + 1, rvalue.begin() + idx_last_quote);
if (std::count(rvalue.begin(), rvalue.end(), '\"') != 0)
{
//std::cerr << filename << ':' << line_num << ": Warning: Extraneous quotes present on line." << std::endl;
ErrorLog("%s:%d: Warning: Extraneous quotes present on line.", filename.c_str(), line_num);
}
// In INI files, we do not allow multiple settings with the same key. If
// a setting is specified multiple times, previous ones are overwritten.
if (current_section[lvalue].Empty())
current_section.Create(lvalue, rvalue);
else
current_section.Get(lvalue).SetValue(rvalue);
}
Node::Ptr_t FromINIFile(const std::string &filename)
{
std::ifstream file;
file.open(filename);
if (file.fail())
{
ErrorLog("Unable to open '%s'. Configuration will not be loaded.", filename.c_str());
return CreateEmpty();
}
Node::Ptr_t global_ptr = std::make_shared<Node>("Global"); // the root is also the "Global" section
Node &global = *global_ptr;
Node *current_section = &global;
size_t line_num = 1;
while (!file.eof())
{
if (file.fail())
{
ErrorLog("%s:%d: File read error. Configuration will be incomplete.");
return CreateEmpty();
}
std::string line;
std::getline(file, line);
line = StripComment(line);
line = TrimWhiteSpace(line);
if (!line.empty())
{
if (*line.begin() == '[' && *line.rbegin() == ']')
{
// Check if section exists else create a new one
std::string section(TrimWhiteSpace(std::string(line.begin() + 1, line.begin() + line.length() - 1)));
if (section.empty() || section == "Global") // empty section names (e.g., "[]") assigned to "[ Global ]"
current_section = &global;
else if (global[section].Empty())
{
Node &new_section = global.Create(section);
current_section = &new_section;
}
else
current_section = &global.Get(section);
}
else
{
ParseAssignment(*current_section, filename, line_num, line);
}
}
line_num++;
}
return global_ptr;
}
/*
* Produces a new INI section by merging two existing sections.
*
* - Nodes from x and y with children are ignored as per INI semantics.
* The presence of children indicates a section, not a setting.
* - Settings from y override settings in x if already present.
* - x's key is retained.
*/
Node::Ptr_t MergeINISections(const Node &x, const Node &y)
{
Node::Ptr_t merged_ptr = std::make_shared<Node>(x.Key()); // result has same key as x
Node &merged = *merged_ptr;
// First copy settings from section x
for (auto it = x.begin(); it != x.end(); ++it)
{
if (it->IsLeaf())
merged.Create(it->Key(), it->Value());
}
// Merge in settings from section y
for (auto it = y.begin(); it != y.end(); ++it)
{
auto &key = it->Key();
auto &value = it->Value();
if (it->IsLeaf())
{
if (merged[key].Empty())
merged.Create(key, value);
else
merged.Get(key).SetValue(value);
}
}
return merged_ptr;
}
static void WriteSection(std::ofstream &file, const Node &section)
{
file << "[ " << section.Key() << " ]" << std::endl;
file << std::endl;
for (auto it = section.begin(); it != section.end(); ++it)
{
if (it->IsLeaf())
file << it->Key() << " = \"" << it->Value() << "\"" << std::endl;
}
file << std::endl << std::endl;
}
void WriteINIFile(const std::string &filename, const Node &config, const std::string &header_comment)
{
std::ofstream file;
file.open(filename);
if (file.fail())
{
ErrorLog("Unable to write to '%s'. Configuration will not be saved.");
return;
}
if (!header_comment.empty())
file << header_comment << std::endl << std::endl;
WriteSection(file, config);
for (auto it = config.begin(); it != config.end(); ++it)
{
if (it->HasChildren())
WriteSection(file, *it);
}
}
} // Config
} // Util

204
Src/Util/NewConfig.h Normal file
View file

@ -0,0 +1,204 @@
#ifndef INCLUDED_UTIL_CONFIG_H
#define INCLUDED_UTIL_CONFIG_H
#include "Util/Format.h"
#include <map>
#include <memory>
#include <iterator>
namespace Util
{
namespace Config
{
class Node
{
public:
typedef std::shared_ptr<Node> Ptr_t;
typedef std::shared_ptr<const Node> ConstPtr_t;
private:
Ptr_t m_next_sibling;
Ptr_t m_first_child;
Ptr_t m_last_child;
std::map<std::string, Ptr_t> m_children;
const std::string m_key;
std::string m_value;
static Node s_empty_node; // key, value, and children must always be empty
void AddChild(Ptr_t &node);
Node(); // prohibit accidental/unintentional creation of blank nodes
friend Ptr_t CreateEmpty();
public:
class const_iterator;
class iterator: public std::iterator<std::forward_iterator_tag, Node>
{
private:
Ptr_t m_node;
friend class const_iterator;
public:
inline iterator(Ptr_t node = Ptr_t())
: m_node(node)
{}
inline iterator(const iterator &it)
: m_node(it.m_node)
{}
inline iterator operator++()
{
// Prefix increment
m_node = m_node->m_next_sibling;
return *this;
}
inline iterator operator++(int)
{
// Postfix increment
iterator current(*this);
m_node = m_node->m_next_sibling;
return *this;
}
inline Node &operator*() const
{
return *m_node;
}
inline Ptr_t operator->() const
{
return m_node;
}
inline bool operator==(const iterator &rhs) const
{
return m_node == rhs.m_node;
}
inline bool operator!=(const iterator &rhs) const
{
return m_node != rhs.m_node;
}
};
class const_iterator: public std::iterator<std::forward_iterator_tag, const Node>
{
private:
ConstPtr_t m_node;
public:
inline const_iterator(ConstPtr_t node = ConstPtr_t())
: m_node(node)
{}
inline const_iterator(Ptr_t node = Ptr_t())
: m_node(std::const_pointer_cast<const Node>(node))
{}
inline const_iterator(const const_iterator &it)
: m_node(it.m_node)
{}
inline const_iterator(const Node::iterator &it)
: m_node(it.m_node)
{}
inline const_iterator operator++()
{
// Prefix increment
m_node = m_node->m_next_sibling;
return *this;
}
inline const_iterator operator++(int)
{
// Postfix increment
iterator current(*this);
m_node = m_node->m_next_sibling;
return *this;
}
inline const Node &operator*() const
{
return *m_node;
}
inline ConstPtr_t operator->() const
{
return m_node;
}
inline bool operator==(const const_iterator &rhs) const
{
return m_node == rhs.m_node;
}
inline bool operator!=(const const_iterator &rhs) const
{
return m_node != rhs.m_node;
}
};
inline iterator begin()
{
return iterator(m_first_child);
}
inline iterator end()
{
return iterator();
}
inline const_iterator begin() const
{
return iterator(m_first_child);
}
inline const_iterator end() const
{
return iterator();
}
const inline std::string &Key() const
{
return m_key;
}
const inline std::string &ValueWithDefault(const std::string &default_value) const
{
if (this == &s_empty_node)
return default_value;
return m_value;
}
const inline std::string &Value() const
{
//TODO: if empty node, throw exception?
return m_value;
}
inline void SetValue(const std::string &value)
{
if (this != &s_empty_node) // not allowed to modify empty node
m_value = value;
}
inline bool Empty() const
{
return m_key.empty();
}
inline bool IsLeaf() const
{
return m_children.empty();
}
inline bool HasChildren() const
{
return !IsLeaf();
}
Node &Get(const std::string &path);
const Node &operator[](const std::string &path) const;
const Node &Get(const std::string &path) const;
void Print(size_t indent_level = 0) const;
Node &Create(const std::string &key);
Node &Create(const std::string &key, const std::string &value);
Node(const std::string &key);
Node(const std::string &key, const std::string &value);
Node(const Node &that);
~Node();
};
Node::Ptr_t CreateEmpty();
Node::Ptr_t FromINIFile(const std::string &filename);
Node::Ptr_t MergeINISections(const Node &x, const Node &y);
void WriteINIFile(const std::string &filename, const Node &config, const std::string &header_comment);
} // Config
} // Util
#endif // INCLUDED_UTIL_CONFIG_H

61
Src/Util/Test_Config.cpp Normal file
View file

@ -0,0 +1,61 @@
#include "Util/NewConfig.h"
#include <iostream>
int main()
{
/*
* Creates a config tree corresponding to the following:
*
* <game>
* <name>scud</name>
* <roms>
* <crom>
* <name>foo.bin</name>
* <offset>0</offset>
* </crom>
* <crom>
* <name>bar.bin</name>
* <offset>2</offset>
* </crom>
* </roms>
* </game>
*/
Util::Config::Node::Ptr_t root = std::make_shared<Util::Config::Node>("global");
auto &game = root->Create("game");
game.Create("name", "scud");
auto &roms = game.Create("roms");
auto &crom1 = roms.Create("crom");
crom1.Create("name", "foo.bin");
crom1.Create("offset", "0");
auto &crom2 = roms.Create("crom");
crom2.Create("name", "bar.bin");
crom2.Create("offset", "2");
const char *expected_output =
"global children={ game } \n"
" game children={ name roms } \n"
" name=scud children={ } \n"
" roms children={ crom } \n"
" crom children={ name offset } \n"
" name=foo.bin children={ } \n"
" offset=0 children={ } \n"
" crom children={ name offset } \n"
" name=bar.bin children={ } \n"
" offset=2 children={ } \n";
std::cout << "Expected output:" << std::endl << std::endl << expected_output << std::endl;
std::cout << "Actual config tree:" << std::endl << std::endl;
root->Print();
std::cout << std::endl;
// Expect second crom: bar.bin
auto &global = *root;
std::cout << "game/roms/crom/name=" << global["game/roms/crom/name"].Value() << " (expected: bar.bin)" << std::endl << std::endl;
// Make a copy
std::cout << "Copy:" << std::endl << std::endl;
Util::Config::Node::Ptr_t copy = std::make_shared<Util::Config::Node>(*root);
copy->Print();
std::cout << std::endl;
return 0;
}