]> git.tdb.fi Git - libs/core.git/commitdiff
Merge branch 'strings-master'
authorMikko Rasa <tdb@tdb.fi>
Wed, 25 May 2011 13:46:08 +0000 (16:46 +0300)
committerMikko Rasa <tdb@tdb.fi>
Wed, 25 May 2011 13:46:08 +0000 (16:46 +0300)
Conflicts:
.gitignore
Build
Changelog.txt

41 files changed:
.gitignore
Build
Changelog.txt
source/core/application.cpp [new file with mode: 0644]
source/core/application.h [new file with mode: 0644]
source/core/except.cpp [new file with mode: 0644]
source/core/except.h [new file with mode: 0644]
source/core/getopt.cpp [new file with mode: 0644]
source/core/getopt.h [new file with mode: 0644]
source/core/main.cpp [new file with mode: 0644]
source/core/meta.h [new file with mode: 0644]
source/core/mutex.h [new file with mode: 0644]
source/core/refptr.h [new file with mode: 0644]
source/core/semaphore.cpp [new file with mode: 0644]
source/core/semaphore.h [new file with mode: 0644]
source/core/thread.cpp [new file with mode: 0644]
source/core/thread.h [new file with mode: 0644]
source/core/types.h [new file with mode: 0644]
source/core/variant.h [new file with mode: 0644]
source/debug/backtrace.cpp [new file with mode: 0644]
source/debug/backtrace.h [new file with mode: 0644]
source/debug/demangle.cpp [new file with mode: 0644]
source/debug/demangle.h [new file with mode: 0644]
source/debug/profiler.cpp [new file with mode: 0644]
source/debug/profiler.h [new file with mode: 0644]
source/debug/profilingscope.cpp [new file with mode: 0644]
source/debug/profilingscope.h [new file with mode: 0644]
source/time/datetime.cpp [new file with mode: 0644]
source/time/datetime.h [new file with mode: 0644]
source/time/timedelta.cpp [new file with mode: 0644]
source/time/timedelta.h [new file with mode: 0644]
source/time/timer.cpp [new file with mode: 0644]
source/time/timer.h [new file with mode: 0644]
source/time/timestamp.h [new file with mode: 0644]
source/time/timezone.cpp [new file with mode: 0644]
source/time/timezone.h [new file with mode: 0644]
source/time/types.h [new file with mode: 0644]
source/time/units.cpp [new file with mode: 0644]
source/time/units.h [new file with mode: 0644]
source/time/utils.cpp [new file with mode: 0644]
source/time/utils.h [new file with mode: 0644]

index 6191adc25630e161d8038cfe0a6f431c8f58bccd..82b1d620d50afd20e80a43f73c10c2727cf0261a 100644 (file)
@@ -1,13 +1,15 @@
 /.deps
 /.options.*
 /.profile
+/arm
 /debug
 /grep
+/libmspcore.a
+/libmspcore.so
 /libmspstrings.a
 /libmspstrings.so
-/mspstrings.pc
+/mspcore.pc
 /release
 /temp
 /transcode
 /win32
-
diff --git a/Build b/Build
index 06545dc50a21490049519164b0f194ce3edb1c74..e1f3fd7dc5bffe5140a0bf7449ce1d11716ff788 100644 (file)
--- a/Build
+++ b/Build
@@ -1,11 +1,45 @@
-// $Id$
+/* $Id$ */
 
-package "mspstrings"
+package "mspcore"
 {
        version "1.1";
-       description "String utilities library";
+       description "Mikkosoft Productions core library";
 
-       require "mspcore";
+       require "sigc++-2.0";
+       if "arch!=win32"
+       {
+               build_info
+               {
+                       library "pthread";
+               };
+       };
+       if "arch=linux"
+       {
+               build_info
+               {
+                       library "dl";
+               };
+       };
+
+       feature "exception_backtrace" "Generate a backtrace when an exception is thrown.";
+
+       headers "msp/core"
+       {
+               source "source/core";
+               install true;
+       };
+
+       headers "msp/time"
+       {
+               source "source/time";
+               install true;
+       };
+
+       headers "msp/debug"
+       {
+               source "source/debug";
+               install true;
+       };
 
        headers "msp/strings"
        {
@@ -19,8 +53,11 @@ package "mspstrings"
                install true;
        };
 
-       library "mspstrings"
+       library "mspcore"
        {
+               source "source/core";
+               source "source/debug";
+               source "source/time";
                source "source/strings";
                source "source/stringcodec";
                install true;
@@ -31,7 +68,7 @@ package "mspstrings"
                source "grep.cpp";
                build_info
                {
-                       library "mspstrings";
+                       library "mspcore";
                };
        };
 
@@ -40,7 +77,7 @@ package "mspstrings"
                source "transcode.cpp";
                build_info
                {
-                       library "mspstrings";
+                       library "mspcore";
                };
        };
 
index 9af98cd6ced9e25f205e2c716b89293c0d93a025..a9d3619be45d92025cc30dc98f8ffef0c1a3015e 100644 (file)
@@ -1,3 +1,21 @@
+2.0
+
+== Changes from pre-2.0 mspcore ==
+
+1.1
+* Time zone support
+* Some more time operations
+* Bugfixes
+  - Compatibility fixes for FreeBSD and 64-bit systems
+  - Proper handling of string arguments with spaces in GetOpt
+  - DateTime addition fixes
+  - Timer no longer drops duplicate timeouts
+
+1.0
+* First released version
+
+== Changes from pre-2.0 mspstrings ==
+
 1.1
 * Codec autodetection
 * lexical_cast rewritten from scratch
diff --git a/source/core/application.cpp b/source/core/application.cpp
new file mode 100644 (file)
index 0000000..139f94f
--- /dev/null
@@ -0,0 +1,161 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2006-2008, 2011  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#include <signal.h>
+#include <iostream>
+#include <typeinfo>
+#include "../debug/backtrace.h"
+#include "../debug/demangle.h"
+#include "../time/units.h"
+#include "../time/utils.h"
+#include "application.h"
+#include "except.h"
+
+using namespace std;
+
+namespace Msp {
+
+Application *Application::app_ = 0;
+Application::Starter *Application::starter_ = 0;
+void *Application::data_ = 0;
+
+Application::Application():
+       exit_code(0)
+{ }
+
+/**
+Constructs an instance of the registered application class and runs it.  If the
+application throws a UsageError, the static usage() function is called.
+
+This function can only be called once.  The global main() function provided by
+the library normally does it automatically at program startup.
+*/
+int Application::run(int argc, char **argv, void *data)
+{
+       static bool called = false;
+       if(called)
+       {
+               cerr<<"Trying to call Application::run_app twice!\n";
+               return 125;
+       }
+       called = true;
+
+       if(!starter_)
+       {
+               cerr<<"Trying to run with no RegisteredApplication class!\n";
+               return 126;
+       }
+
+       data_ = data;
+
+       try
+       {
+               try
+               {
+                       app_ = starter_->create_app(argc, argv);
+               }
+               catch(const UsageError &e)
+               {
+                       starter_->usage(e.what(), argv[0], e.get_brief());
+                       return 1;
+               }
+
+               int result = app_->main();
+               Application *a = app_;
+               app_ = 0;
+               delete a;
+               return result;
+       }
+       catch(const exception &e)
+       {
+               delete app_;
+
+#ifdef WIN32
+               string msg = Debug::demangle(typeid(e).name())+":\n"+e.what();
+               MessageBoxA(0, msg.c_str(), "Uncaught exception", MB_OK|MB_ICONERROR);
+#else
+               cerr<<"An uncaught exception occurred.\n";
+               cerr<<"  type:   "<<Debug::demangle(typeid(e).name())<<'\n';
+               cerr<<"  what(): "<<e.what()<<'\n';
+
+               const Exception *exc = dynamic_cast<const Exception *>(&e);
+               if(exc && !exc->get_backtrace().get_frames().empty())
+               {
+                       cerr<<"  backtrace:\n";
+                       const list<Debug::Backtrace::StackFrame> &frames = exc->get_backtrace().get_frames();
+                       for(list<Debug::Backtrace::StackFrame>::const_iterator i=frames.begin(); i!=frames.end(); ++i)
+                               cerr<<"    "<<*i<<'\n';
+               }
+#endif
+
+               return 124;
+       }
+}
+
+/**
+Prints a message describing the usage of the application.  The default version
+will blame the programmer for being lazy.
+
+@param   reason  Why the function was called
+@param   argv0   The value of argv[0], to be used in the message
+@param   brief   Whether to print a brief or long usage message
+*/
+void Application::usage(const char *reason, const char *, bool)
+{
+       if(reason)
+               cerr<<"UsageError: "<<reason<<'\n';
+       cerr<<"The programmer was lazy and didn't write a usage() function for this application.\n";
+}
+
+/**
+Default main loop.  Calls tick() repeatedly until exit() is called.  A custom
+main loop should monitor the done member variable and return exit_code.
+*/
+int Application::main()
+{
+       done = false;
+       while(!done)
+               tick();
+
+       return exit_code;
+}
+
+/**
+Sets the specified signal to be delivered to the sighandler member function.
+*/
+void Application::catch_signal(int s)
+{
+       signal(s, &sighandler_);
+}
+
+/**
+Causes the application to exit gracefully with the given exit code.
+*/
+void Application::exit(int c)
+{
+       done = true;
+       exit_code = c;
+}
+
+/**
+Static wrapper function to call a member function of the Application instance.
+*/
+void Application::sighandler_(int s)
+{
+       app_->sighandler(s);
+}
+
+
+Application::Starter::Starter()
+{
+       if(starter_)
+               throw InvalidState("Can't create more than one Starter instance");
+
+       starter_ = this;
+}
+
+} // namespace Msp
diff --git a/source/core/application.h b/source/core/application.h
new file mode 100644 (file)
index 0000000..b51ac3c
--- /dev/null
@@ -0,0 +1,84 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2006-2008, 2011  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_CORE_APPLICATION_H_
+#define MSP_CORE_APPLICATION_H_
+
+namespace Msp {
+
+/**
+Base class for applications.  Inherit the main class from this and add a static
+member of type RegApp<MainClass>.
+*/
+class Application
+{
+protected:
+       class Starter
+       {
+       protected:
+               Starter();
+       public:
+               virtual ~Starter() { }
+
+               virtual Application *create_app(int, char **) = 0;
+               virtual void usage(const char *, const char *, bool) = 0;
+       };
+
+       bool done;
+       int exit_code;
+
+private:
+       static Starter *starter_;
+       static Application *app_;
+       static void *data_;
+
+       Application(const Application &);
+       Application &operator=(const Application &);
+protected:
+       Application();
+public:
+       virtual ~Application() { }
+
+       static int run(int, char **, void * =0);
+       static void usage(const char *, const char *, bool);
+       static void *get_data() { return data_; }
+
+protected:
+       virtual int main();
+       void catch_signal(int);
+       void exit(int);
+       virtual void tick() { }
+       virtual void sighandler(int) { }
+private:
+       static void sighandler_(int);
+};
+
+
+template<typename T>
+class RegisteredApplication: public Application
+{
+private:
+       class Starter: public Application::Starter
+       {
+       public:
+               Application *create_app(int argc, char **argv) { return new T(argc, argv); }
+               void usage(const char *r, const char *a, bool b) { T::usage(r, a, b); }
+       };
+
+       static Starter starter_;
+
+protected:
+       // Force the starter into existence
+       RegisteredApplication() { (void)starter_; }
+};
+
+template<typename T>
+typename RegisteredApplication<T>::Starter RegisteredApplication<T>::starter_;
+
+} // namespace Msp
+
+#endif
diff --git a/source/core/except.cpp b/source/core/except.cpp
new file mode 100644 (file)
index 0000000..bdcdd7d
--- /dev/null
@@ -0,0 +1,62 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2006-2008  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#include <sstream>
+#include <cstring>
+#ifdef WIN32
+#include <windows.h>
+#endif
+#include "except.h"
+
+using namespace std;
+
+namespace Msp {
+
+Exception::Exception(const string &w):
+       wot(w)
+{
+#ifdef WITH_EXCEPTION_BACKTRACE
+       bt = Debug::Backtrace::create();
+#endif
+}
+
+Exception &Exception::at(const std::string &w) throw()
+{
+       wer = w;
+       wot = wer+": "+wot;
+       return *this;
+}
+
+
+SystemError::SystemError(const string &w_, int e):
+       Exception(build_what(w_, e)),
+       err(e)
+{ }
+
+string SystemError::build_what(const string &w, int e)
+{
+       ostringstream buf;
+       buf<<w<<": ";
+#ifdef WIN32
+       char msg[1024];
+       if(FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, 0, e, 0, msg, sizeof(msg), 0))
+               buf<<msg;
+       else
+               buf<<e;
+#else
+       buf<<strerror(e);
+#endif
+       return buf.str();
+}
+
+
+KeyError::KeyError(const string &w_, const string &k):
+       Exception(w_+" ("+k+")"),
+       key(k)
+{ }
+
+} // namespace Msp
diff --git a/source/core/except.h b/source/core/except.h
new file mode 100644 (file)
index 0000000..a58b3ca
--- /dev/null
@@ -0,0 +1,115 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2006-2008  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_CORE_ERROR_H_
+#define MSP_CORE_ERROR_H_
+
+#include <exception>
+#include <string>
+#include "../debug/backtrace.h"
+
+namespace Msp {
+
+/**
+Base class for all Msp exceptions.
+*/
+class Exception: public std::exception
+{
+private:
+       std::string wot;
+       std::string wer;
+       Debug::Backtrace bt;
+
+public:
+       Exception(const std::string &);
+       ~Exception() throw() { }
+
+       const char *what() const throw() { return wot.c_str(); }
+       Exception &at(const std::string &) throw();
+       const char *where() const throw() { return wer.c_str(); }
+       const Debug::Backtrace &get_backtrace() const throw() { return bt; }
+};
+
+/**
+Thrown when a function parameter has an invalid value.
+*/
+class InvalidParameterValue: public Exception
+{
+public:
+       InvalidParameterValue(const std::string &w_): Exception(w_) { }
+};
+
+/**
+Thrown when a lookup from a map fails.
+*/
+class KeyError: public Exception
+{
+private:
+       std::string key;
+
+public:
+       KeyError(const std::string &w_): Exception(w_) { }
+       KeyError(const std::string &w_, const std::string &k);
+       ~KeyError() throw() { }
+
+       const std::string &get_key() const { return key; }
+};
+
+/**
+Thrown when the current object state doesn't allow the requested action.
+*/
+class InvalidState: public Exception
+{
+public:
+       InvalidState(const std::string &w_): Exception(w_) { }
+};
+
+/**
+Thrown when the application is invoked with wrong parameters.
+*/
+class UsageError: public Exception
+{
+private:
+       bool brief;
+
+public:
+       UsageError(const std::string &r, bool b = true): Exception(r), brief(b) { }
+       bool get_brief() const { return brief; }
+};
+
+/**
+Thrown when a system call fails.
+*/
+class SystemError: public Exception
+{
+private:
+       int err;
+
+public:
+       SystemError(const std::string &, int);
+       int get_error_code() const { return err; }
+
+private:
+       static std::string build_what(const std::string &, int);
+};
+
+/**
+Thrown when "impossible" things happen.
+*/
+class LogicError: public Exception
+{
+public:
+       LogicError(const std::string &w_): Exception(w_) { }
+};
+
+template<typename E>
+void throw_at(E e, const std::string &a)
+{ e.at(a); throw e; }
+
+} // namespace Msp
+
+#endif
diff --git a/source/core/getopt.cpp b/source/core/getopt.cpp
new file mode 100644 (file)
index 0000000..74f1476
--- /dev/null
@@ -0,0 +1,244 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2006-2009, 2011 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#include "getopt.h"
+
+using namespace std;
+
+namespace Msp {
+
+GetOpt::~GetOpt()
+{
+       for(list<OptBase *>::iterator i=opts.begin(); i!=opts.end(); ++i)
+               delete *i;
+}
+
+GetOpt::OptBase &GetOpt::get_option(char s)
+{
+       for(list<OptBase *>::iterator i=opts.begin(); i!=opts.end(); ++i)
+               if((*i)->get_short()==s)
+                       return **i;
+       throw UsageError(string("Unknown option -")+s);
+}
+
+GetOpt::OptBase &GetOpt::get_option(const string &l)
+{
+       for(list<OptBase *>::iterator i=opts.begin(); i!=opts.end(); ++i)
+               if((*i)->get_long()==l)
+                       return **i;
+       throw UsageError(string("Unknown option --")+l);
+}
+
+void GetOpt::operator()(unsigned argc, const char *const *argv)
+{
+       unsigned i = 1;
+       for(; i<argc;)
+       {
+               if(argv[i][0]=='-')
+               {
+                       if(argv[i][1]=='-')
+                       {
+                               if(!argv[i][2])
+                                       break;
+
+                               i += process_long(argv+i);
+                       }
+                       else
+                               i += process_short(argv+i);
+               }
+               else
+                       args.push_back(argv[i++]);
+       }
+       
+       for(; i<argc; ++i)
+               args.push_back(argv[i]);
+}
+
+unsigned GetOpt::process_long(const char *const *argp)
+{
+       // Skip the --
+       const char *arg = argp[0]+2;
+
+       // See if the argument contains an =
+       unsigned equals = 0;
+       for(; arg[equals] && arg[equals]!='='; ++equals) ;
+       
+       OptBase &opt = get_option(string(arg, equals));
+       
+       if(arg[equals])
+               // Process the part after the = as option argument
+               opt.process(arg+equals+1);
+       else if(opt.get_arg_type()==REQUIRED_ARG)
+       {
+               if(!argp[1])
+                       throw UsageError("Premature end of arguments");
+
+               // Process the next argument as option argument
+               opt.process(argp[1]);
+               return 2;
+       }
+       else
+               opt.process();
+       
+       return 1;
+}
+
+unsigned GetOpt::process_short(const char *const *argp)
+{
+       // Skip the -
+       const char *arg = argp[0]+1;
+
+       // Loop through all characters in the argument
+       for(; *arg; ++arg)
+       {
+               OptBase &opt = get_option(*arg);
+
+               if(arg[1] && opt.get_arg_type()!=NO_ARG)
+               {
+                       // Need an option argument and we have characters left - use them
+                       opt.process(arg+1);
+                       return 1;
+               }
+               else if(opt.get_arg_type()==REQUIRED_ARG)
+               {
+                       if(!argp[1])
+                               throw UsageError("Premature end of arguments");
+                       
+                       // Use the next argument as option argument
+                       opt.process(argp[1]);
+                       return 2;
+               }
+               else
+                       opt.process();
+       }
+
+       return 1;
+}
+
+string GetOpt::generate_usage(const string &argv0) const
+{
+       ostringstream line;
+       
+       line<<argv0;
+       for(list<OptBase *>::const_iterator i=opts.begin(); i!=opts.end(); ++i)
+       {
+               line<<" [";
+               if((*i)->get_short())
+               {
+                       line<<'-'<<(*i)->get_short();
+                       if(!(*i)->get_long().empty())
+                               line<<'|';
+                       else if((*i)->get_arg_type()==OPTIONAL_ARG)
+                               line<<'['<<(*i)->get_metavar()<<']';
+                       else if((*i)->get_arg_type()==REQUIRED_ARG)
+                               line<<' '<<(*i)->get_metavar();
+               }
+               if(!(*i)->get_long().empty())
+               {
+                       line<<"--"<<(*i)->get_long();
+
+                       if((*i)->get_arg_type()==OPTIONAL_ARG)
+                               line<<"[="<<(*i)->get_metavar()<<']';
+                       else if((*i)->get_arg_type()==REQUIRED_ARG)
+                               line<<'='<<(*i)->get_metavar();
+               }
+               line<<']';
+       }
+
+       return line.str();
+}
+
+string GetOpt::generate_help() const
+{
+       bool any_short = false;
+       for(list<OptBase *>::const_iterator i=opts.begin(); (!any_short && i!=opts.end()); ++i)
+               any_short = (*i)->get_short();
+
+       string::size_type maxw = 0;
+       list<string> switches;
+       for(list<OptBase *>::const_iterator i=opts.begin(); i!=opts.end(); ++i)
+       {
+               ostringstream swtch;
+               if((*i)->get_short())
+               {
+                       swtch<<'-'<<(*i)->get_short();
+                       if(!(*i)->get_long().empty())
+                               swtch<<", ";
+                       else if((*i)->get_arg_type()==OPTIONAL_ARG)
+                               swtch<<'['<<(*i)->get_metavar()<<']';
+                       else if((*i)->get_arg_type()==REQUIRED_ARG)
+                               swtch<<' '<<(*i)->get_metavar();
+               }
+               else if(any_short)
+                       swtch<<"    ";
+               if(!(*i)->get_long().empty())
+               {
+                       swtch<<"--"<<(*i)->get_long();
+
+                       if((*i)->get_arg_type()==OPTIONAL_ARG)
+                               swtch<<"[="<<(*i)->get_metavar()<<']';
+                       else if((*i)->get_arg_type()==REQUIRED_ARG)
+                               swtch<<'='<<(*i)->get_metavar();
+               }
+               switches.push_back(swtch.str());
+               maxw = max(maxw, switches.back().size());
+       }
+
+       string result;
+       list<string>::const_iterator j = switches.begin();
+       for(list<OptBase *>::const_iterator i=opts.begin(); i!=opts.end(); ++i, ++j)
+       {
+               result += "  "+*j;
+               result += string(maxw+2-j->size(), ' ');
+               result += (*i)->get_help();
+               result += '\n';
+       }
+       
+       return result;
+}
+
+
+GetOpt::OptBase::OptBase(char s, const std::string &l, ArgType a):
+       shrt(s),
+       lng(l),
+       arg_type(a),
+       seen_count(0),
+       metavar("ARG")
+{ }
+
+GetOpt::OptBase &GetOpt::OptBase::set_help(const string &h)
+{
+       help = h;
+       return *this;
+}
+
+GetOpt::OptBase &GetOpt::OptBase::set_help(const string &h, const string &m)
+{
+       help = h;
+       metavar = m;
+       return *this;
+}
+
+void GetOpt::OptBase::process()
+{
+       if(arg_type==REQUIRED_ARG)
+               throw UsageError("--"+lng+" requires an argument");
+       ++seen_count;
+
+       store();
+}
+
+void GetOpt::OptBase::process(const string &arg)
+{
+       if(arg_type==NO_ARG)
+               throw UsageError("--"+lng+" takes no argument");
+       ++seen_count;
+
+       store(arg);
+}
+
+} // namespace Msp
diff --git a/source/core/getopt.h b/source/core/getopt.h
new file mode 100644 (file)
index 0000000..8e0f49a
--- /dev/null
@@ -0,0 +1,159 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2006-2009, 2011 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_CORE_GETOPT_H_
+#define MSP_CORE_GETOPT_H_
+
+#include <sstream>
+#include <string>
+#include <vector>
+#include "except.h"
+
+namespace Msp {
+
+class GetOpt
+{
+public:
+       enum ArgType
+       {
+               NO_ARG,
+               OPTIONAL_ARG,
+               REQUIRED_ARG
+       };
+       
+       class OptBase
+       {
+       protected:
+               char shrt;
+               std::string lng;
+               ArgType arg_type;
+               unsigned seen_count;
+               std::string help;
+               std::string metavar;
+
+               OptBase(char, const std::string &, ArgType);
+       public:
+               virtual ~OptBase() { }
+
+               OptBase &set_help(const std::string &);
+               OptBase &set_help(const std::string &, const std::string &);
+               char get_short() const { return shrt; }
+               const std::string &get_long() const { return lng; }
+               ArgType get_arg_type() const { return arg_type; }
+               const std::string &get_help() const { return help; }
+               const std::string &get_metavar() const { return metavar; }
+               unsigned get_seen_count() const { return seen_count; }
+               void process();
+               void process(const std::string &);
+       protected:
+               virtual void store() = 0;
+               virtual void store(const std::string &) = 0;
+       };
+
+private:
+       template<typename T>
+       class Option: public OptBase
+       {
+       public:
+               Option(char s, const std::string &l, T &d, ArgType a): OptBase(s, l, a), data(d) { }
+
+               virtual void store() { }
+
+               virtual void store(const std::string &a)
+               {
+                       T tmp;
+                       std::istringstream ss(a);
+                       ss>>tmp;
+                       if(ss.fail())
+                               throw UsageError("Invalid argument for --"+lng);
+
+                       data = tmp;
+               }
+       private:
+               T &data;
+       };
+
+       template<typename T>
+       class ListOption: public OptBase
+       {
+       public:
+               ListOption(char s, const std::string &l, T &d, ArgType a): OptBase(s, l, a), data(d)
+               { if(arg_type!=REQUIRED_ARG) throw Exception("ListOption with arg_type!=REQUIRED makes no sense"); }
+
+               virtual void store() { }
+
+               virtual void store(const std::string &a)
+               {
+                       typename T::value_type tmp;
+                       std::istringstream ss(a);
+                       ss>>tmp;
+                       if(ss.fail())
+                               throw UsageError("Invalid argument for --"+lng);
+
+                       data.push_back(tmp);
+               }
+       private:
+               T &data;
+       };
+
+       std::list<OptBase *> opts;
+       std::vector<std::string> args;
+
+public:
+       ~GetOpt();
+
+       const std::vector<std::string> &get_args() const { return args; }
+
+       template<typename T>
+       OptBase &add_option(char s, const std::string &l, T &d, ArgType a = NO_ARG)
+       { opts.push_back(new Option<T>(s, l, d, a)); return *opts.back(); }
+       
+       template<typename T>
+       OptBase &add_option(char s, const std::string &l, std::list<T> &d, ArgType a = REQUIRED_ARG)
+       { opts.push_back(new ListOption<std::list<T> >(s, l, d, a)); return *opts.back(); }
+       
+       template<typename T>
+       OptBase &add_option(const std::string &l, T &d, ArgType a)
+       { return add_option(0, l, d, a); }
+
+private:
+       OptBase &get_option(char);
+       OptBase &get_option(const std::string &);
+
+public:
+       /** Processes argc/argv style command line arguments.  The contents of argv
+       will be unchanged; use get_args to access non-option arguments. */
+       void operator()(unsigned, const char *const *);
+
+private:
+       /** Processes a long option.  Returns the number of arguments eaten. */
+       unsigned process_long(const char *const *);
+
+       /** Processes short options.  Returns the number of arguments eaten. */
+       unsigned process_short(const char *const *);
+
+public:
+       /** Generates a single line that describes known options. */
+       std::string generate_usage(const std::string &) const;
+
+       /** Generates help for known options in tabular format, one option per
+       line.  The returned string will have a linefeed at the end. */
+       std::string generate_help() const;
+};
+
+template<> inline void GetOpt::Option<bool>::store()     { data = true; }
+template<> inline void GetOpt::Option<unsigned>::store() { ++data; }
+
+template<> inline void GetOpt::Option<std::string>::store(const std::string &a)
+{ data = a; }
+
+template<> inline void GetOpt::ListOption<std::list<std::string> >::store(const std::string &a)
+{ data.push_back(a); }
+
+} // namespace Msp
+
+#endif
diff --git a/source/core/main.cpp b/source/core/main.cpp
new file mode 100644 (file)
index 0000000..f73ef81
--- /dev/null
@@ -0,0 +1,24 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2006 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include "application.h"
+
+#ifdef WIN32
+int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nCmdShow*/)
+{ 
+       int argc        = 0;
+       LPWSTR *argv = CommandLineToArgvW(GetCommandLineW(), &argc);
+       return Msp::Application::run(argc, (char **)argv, hInstance);
+}
+#endif
+
+int main(int argc, char **argv)
+{ return Msp::Application::run(argc, argv); }
diff --git a/source/core/meta.h b/source/core/meta.h
new file mode 100644 (file)
index 0000000..f6dff16
--- /dev/null
@@ -0,0 +1,31 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2008  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_CORE_META_H_
+#define MSP_CORE_META_H_
+
+namespace Msp {
+
+template<typename T>
+struct RemoveConst
+{ typedef T Type; };
+
+template<typename T>
+struct RemoveConst<const T>
+{ typedef T Type; };
+
+template<typename T>
+struct RemoveReference
+{ typedef T Type; };
+
+template<typename T>
+struct RemoveReference<T &>
+{ typedef T Type; };
+
+} // namespace Msp
+
+#endif
diff --git a/source/core/mutex.h b/source/core/mutex.h
new file mode 100644 (file)
index 0000000..c0c2682
--- /dev/null
@@ -0,0 +1,102 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2006 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_CORE_MUTEX_H_
+#define MSP_CORE_MUTEX_H_
+
+#include "refptr.h"
+#include "types.h"
+
+namespace Msp {
+
+class Mutex
+{
+       friend class Semaphore;
+
+private:
+       MutexHandle mutex;
+
+public:
+#ifndef WIN32
+       Mutex()       { pthread_mutex_init(&mutex, 0); }
+       int lock()    { return pthread_mutex_lock(&mutex); }
+       int trylock() { return pthread_mutex_trylock(&mutex); }
+       int unlock()  { return pthread_mutex_unlock(&mutex); }
+       ~Mutex()      { pthread_mutex_destroy(&mutex); }
+#else
+       Mutex()       { mutex = CreateMutex(0, false, 0); }
+       int lock()    { return WaitForSingleObject(mutex, INFINITE)==WAIT_OBJECT_0; }
+       int trylock() { return WaitForSingleObject(mutex, 0)==WAIT_OBJECT_0; }
+       int unlock()  { return !ReleaseMutex(mutex); }
+       ~Mutex()      { CloseHandle(mutex); }
+#endif
+};
+
+/**
+Locks the mutex for te lifetime of the object.
+*/
+class MutexLock
+{
+private:
+       Mutex &mutex;
+
+public:
+       MutexLock(Mutex &m, bool l = true): mutex(m) { if(l) mutex.lock(); }
+       ~MutexLock() { mutex.unlock(); }
+
+       int lock() { return mutex.lock(); }
+private:
+       MutexLock(const MutexLock &);
+       MutexLock &operator=(const MutexLock &);
+};
+
+/**
+Protects a pointer with a mutex.  As long as the MutexPtr (or a copy of it)
+exists, the mutex will stay locked.
+*/
+template<typename T>
+class MutexPtr
+{
+public:
+       MutexPtr(T *d, Mutex &m): mutex(new MutexLock(m)), data(d) { }
+
+       T &operator*() const { return *data; }
+       T *operator->() const { return data; }
+       void clear() { mutex=0; data = 0; }
+private:
+       RefPtr<MutexLock> mutex;
+       T *data;
+};
+
+/*template<typename T>
+class MutexPtr: public RefCount
+{
+public:
+       MutexPtr(T *d, Mutex &m): mutex(m), data(d) { mutex.lock(); }
+       MutexPtr(const MutexPtr<T> &p): RefCount(p), mutex(p.mutex), data(p.data) { }
+       T &operator*() const { return *data; }
+       T *operator->() const { return data; }
+       void clear() { decref(); data = 0; }
+       ~MutexPtr() { decref(); }
+protected:
+       Mutex &mutex;
+       T     *data;
+
+       bool decref()
+       {
+               if(!RefCount::decref())
+               {
+                       mutex.unlock();
+                       return false;
+               }
+               return true;
+       }
+};*/
+
+}
+
+#endif
diff --git a/source/core/refptr.h b/source/core/refptr.h
new file mode 100644 (file)
index 0000000..20450f1
--- /dev/null
@@ -0,0 +1,131 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2006-2007, 2010-2011 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_CORE_REFPTR_H_
+#define MSP_CORE_REFPTR_H_
+
+namespace Msp {
+
+/**
+A reference counting smart pointer.  When the last RefPtr for the data gets
+destroyed, the data is deleted as well.
+*/
+template<typename T>
+class RefPtr
+{
+       template<typename U> friend class RefPtr;
+
+private:
+       enum
+       {
+               KEEP = 1U<<(sizeof(unsigned)*8-1)
+       };
+
+       T *data;
+       unsigned *count;
+
+public:
+       RefPtr(): data(0), count(0) { }
+       RefPtr(T *d): data(d), count(data ? new unsigned(1) : 0) { }
+private:
+       RefPtr(T *d, unsigned *c): data(d), count(d ? c : 0) { incref(); }
+
+public:
+       /* Must have this or the compiler will generate a default copy-c'tor despite
+       the template version */
+       RefPtr(const RefPtr &p): data(p.data), count(p.count) { incref(); }
+
+       template<typename U>
+       RefPtr(const RefPtr<U> &p): data(p.data), count(p.count) { incref(); }
+
+       ~RefPtr() { decref(); }
+
+       RefPtr &operator=(T *d)
+       {
+               decref();
+               data = d;
+               count = (d ? new unsigned(1) : 0);
+               return *this;
+       }
+
+       // Likewise for the assignment operator
+       RefPtr &operator=(const RefPtr &p) { return assign(p); }
+
+       template<typename U>
+       RefPtr &operator=(const RefPtr<U> &p) { return assign(p); }
+
+private:
+       template<typename U>
+       RefPtr &assign(const RefPtr<U> &p)
+       {
+               decref();
+               data = p.data;
+               count = p.count;
+               incref();
+               return *this;
+       }
+
+public:
+       /** Makes the RefPtr release its reference of the data without deleting it.
+       Note that if there are other RefPtrs left with the same data, it might
+       still get deleted automatically. */
+       T *release()
+       {
+               T *d = data;
+               data = 0;
+               decref();
+               count = 0;
+               return d;
+       }
+
+       /** Marks the data to not be deleted.  This affects all RefPtrs with the
+       same data. */
+       void keep()
+       {
+               if(count)
+                       *count |= KEEP;
+       }
+
+       T *get() const { return data; }
+       T &operator*() const { return *data; }
+       T *operator->() const { return data; }
+       operator bool() const { return data!=0; }
+
+       template<typename U>
+       static RefPtr<T> cast_dynamic(const RefPtr<U> &p)
+       { return RefPtr<T>(dynamic_cast<T *>(p.data), p.count); }
+
+private:
+       void incref()
+       {
+               if(!count) return;
+               ++*count;
+       }
+
+       void decref()
+       {
+               if(!count) return;
+               --*count;
+               if(!*count)
+               {
+                       delete data;
+                       delete count;
+                       data = 0;
+                       count = 0;
+               }
+               else if(*count==KEEP)
+               {
+                       delete count;
+                       data = 0;
+                       count = 0;
+               }
+       }
+};
+
+} // namespace Msp
+
+#endif
diff --git a/source/core/semaphore.cpp b/source/core/semaphore.cpp
new file mode 100644 (file)
index 0000000..139cae2
--- /dev/null
@@ -0,0 +1,122 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2006  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef WIN32
+#include <sys/time.h>
+#endif
+#include <errno.h>
+#include "semaphore.h"
+#include "../time/timestamp.h"
+#include "../time/units.h"
+#include "../time/utils.h"
+
+namespace Msp {
+
+Semaphore::Semaphore():
+       mutex(new Mutex),
+       own_mutex(true)
+{
+       init();
+}
+
+Semaphore::Semaphore(Mutex &m):
+       mutex(&m),
+       own_mutex(false)
+{
+       init();
+}
+
+void Semaphore::init()
+{
+#ifdef WIN32
+       count = 0;
+       sem = CreateSemaphore(0, 0, 32, 0);
+#else
+       pthread_cond_init(&sem, 0);
+#endif
+}
+
+Semaphore::~Semaphore()
+{
+       if(own_mutex)
+               delete mutex;
+#ifdef WIN32
+       CloseHandle(sem);
+#else
+       pthread_cond_destroy(&sem);
+#endif
+}
+
+#ifdef WIN32
+int Semaphore::signal()
+{
+       if(count==0) 
+               return 0;
+
+       int ret = !ReleaseSemaphore(sem, 1, 0); 
+
+       unsigned old_count = count;
+       mutex->unlock();
+       while(count==old_count)
+               Sleep(0);
+       mutex->lock();
+
+       return ret;
+}
+
+int Semaphore::broadcast()
+{
+       if(count==0)
+               return 0;
+       int ret = !ReleaseSemaphore(sem, count, 0);
+
+       mutex->unlock();
+       while(count)
+               Sleep(0);
+       mutex->lock();
+
+       return ret;
+}
+
+int Semaphore::wait()
+{ 
+       ++count; 
+       mutex->unlock();
+       DWORD ret = WaitForSingleObject(sem, INFINITE); 
+       mutex->lock();
+       --count;
+       
+       return ret==WAIT_OBJECT_0;
+}
+#endif
+
+int Semaphore::wait(const Time::TimeDelta &d)
+{
+#ifndef WIN32
+       Time::TimeStamp ts = Time::now()+d;
+
+       timespec timeout;
+       timeout.tv_sec = ts.raw()/1000000;
+       timeout.tv_nsec = (ts.raw()%1000000)*1000;
+
+       int r = pthread_cond_timedwait(&sem, &mutex->mutex, &timeout);
+       if(r==ETIMEDOUT)
+               return 1;
+       else if(r)
+               return -1;
+       return 0;
+#else
+       ++count;
+       mutex->lock();
+       DWORD ret = WaitForSingleObject(sem, (DWORD)(d/Time::usec));
+       mutex->unlock();
+       --count;
+       return ret==WAIT_OBJECT_0;
+#endif
+}
+
+} // namespace Msp
diff --git a/source/core/semaphore.h b/source/core/semaphore.h
new file mode 100644 (file)
index 0000000..e6c09c5
--- /dev/null
@@ -0,0 +1,55 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2006  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_CORE_SEMAPHORE_H_
+#define MSP_CORE_SEMAPHORE_H_
+
+#include "mutex.h"
+#include "types.h"
+#include "../time/timedelta.h"
+
+namespace Msp {
+
+class Semaphore
+{
+private:
+       Mutex *mutex;
+       bool  own_mutex;
+       SemaphoreHandle sem;
+#ifdef WIN32
+       unsigned count;
+#endif
+
+public:
+       Semaphore();
+       Semaphore(Mutex &);
+private:
+       void init();
+public:
+       ~Semaphore();
+
+       int   signal();
+       int   broadcast();
+       int   wait();
+       int   wait(const Time::TimeDelta &);
+       Mutex &get_mutex() { return *mutex; }
+};
+
+#ifndef WIN32
+inline int Semaphore::signal()
+{ return pthread_cond_signal(&sem); }
+
+inline int Semaphore::broadcast()
+{ return pthread_cond_broadcast(&sem); }
+
+inline int Semaphore::wait()
+{ return pthread_cond_wait(&sem, &mutex->mutex); }
+#endif
+
+}
+
+#endif
diff --git a/source/core/thread.cpp b/source/core/thread.cpp
new file mode 100644 (file)
index 0000000..810b1f8
--- /dev/null
@@ -0,0 +1,77 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2006 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef WIN32
+#include <signal.h>
+#endif
+#include "thread.h"
+
+namespace Msp {
+
+/**
+Waits for the thread to exit.  Calling this from the thread will cause a
+deadlock.
+*/
+void Thread::join()
+{
+       if(!launched_)
+               return;
+
+#ifdef WIN32
+       WaitForSingleObject(thread_, INFINITE);
+#else
+       pthread_join(thread_, 0);
+#endif
+       launched_ = false;
+}
+
+/**
+Requests the thread to terminate gracefully.  Currently unimplemented on win32.
+*/
+void Thread::cancel()
+{
+#ifndef WIN32 //XXX
+       pthread_cancel(thread_);
+#endif
+}
+
+/**
+Violently terminates the thread.
+*/
+void Thread::kill()
+{
+#ifdef WIN32
+       TerminateThread(thread_, 0);
+#else
+       pthread_kill(thread_, SIGKILL);
+#endif
+}
+
+Thread::~Thread()
+{
+       if(launched_)
+       {
+               kill();
+               join();
+       }
+}
+
+void Thread::launch()
+{
+       if(launched_)
+               return;
+
+#ifdef WIN32
+       DWORD dummy;  // Win9x needs the lpTthreadId parameter
+       thread_ = CreateThread(0, 0, &main_, this, 0, &dummy);
+#else
+       pthread_create(&thread_, 0, &main_, this);
+#endif
+       launched_ = true;
+}
+
+} // namespace Msp
diff --git a/source/core/thread.h b/source/core/thread.h
new file mode 100644 (file)
index 0000000..92923f3
--- /dev/null
@@ -0,0 +1,56 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2006 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_CORE_THREAD_H_
+#define MSP_CORE_THREAD_H_
+
+#include "types.h"
+
+namespace Msp {
+
+/**
+Base class for threads.  To create a thread for some task, derive it from this
+class and implement the main() function.  Note that threads are not
+automatically started upon creation - you must manually call launch() instead.
+This is to allow initializing variables of the derived class before the thread
+is started.
+*/
+class Thread
+{
+private:
+       ThreadHandle thread_;
+       bool         launched_;
+
+protected:
+       Thread(): launched_(false) { }
+public:
+       virtual ~Thread();
+
+       void join();
+       void cancel();
+       void kill();
+protected:
+       void launch();
+       virtual void main() = 0;
+       void check_cancel();
+
+private:
+       static
+#ifdef WIN32
+       DWORD WINAPI
+#else
+       void *
+#endif
+       main_(void *t) { (reinterpret_cast<Thread *>(t))->main(); return 0; }
+
+       Thread(const Thread &);
+       Thread &operator=(const Thread &);
+};
+
+} // namespace Msp
+
+#endif
diff --git a/source/core/types.h b/source/core/types.h
new file mode 100644 (file)
index 0000000..1bd865d
--- /dev/null
@@ -0,0 +1,31 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2006  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_CORE_TYPES_H_
+#define MSP_CORE_TYPES_H_
+
+#ifdef WIN32
+#include <windows.h>
+#else
+#include <pthread.h>
+#endif
+
+namespace Msp {
+
+#ifdef WIN32
+typedef HANDLE ThreadHandle;
+typedef HANDLE MutexHandle;
+typedef HANDLE SemaphoreHandle;
+#else
+typedef pthread_t ThreadHandle;
+typedef pthread_mutex_t MutexHandle;
+typedef pthread_cond_t SemaphoreHandle;
+#endif
+
+} // namespace Msp
+
+#endif
diff --git a/source/core/variant.h b/source/core/variant.h
new file mode 100644 (file)
index 0000000..8d6db9a
--- /dev/null
@@ -0,0 +1,81 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2008 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_CORE_VARIANT_H_
+#define MSP_CORE_VARIANT_H_
+
+#include "except.h"
+#include "meta.h"
+
+namespace Msp {
+
+class Variant
+{
+private:
+       struct StoreBase
+       {
+               virtual ~StoreBase() { }
+               virtual StoreBase *clone() const =0;
+       };
+
+       template<typename T>
+       struct Store: public StoreBase
+       {
+               T data;
+
+               Store(T d): data(d) { }
+               virtual StoreBase *clone() const { return new Store<T>(data); }
+       };
+
+       StoreBase *store;
+
+public:
+       Variant(): store(0) { }
+       template<typename T>
+       Variant(const T &v): store(new Store<typename RemoveConst<T>::Type>(v)) { }
+       Variant(const Variant &v): store(v.store ? v.store->clone() : 0) { }
+       ~Variant() { delete store; }
+
+       template<typename T>
+       Variant &operator=(const T &v)
+       {
+               delete store;
+               store = new Store<typename RemoveConst<T>::Type>(v);
+               return *this;
+       }
+
+       Variant &operator=(const Variant &v)
+       {
+               delete store;
+               store = (v.store ? v.store->clone() : 0);
+               return *this;
+       }
+
+       template<typename T>
+       T &value() const
+       {
+               typedef typename RemoveConst<T>::Type NCT;
+               Store<NCT> *s = dynamic_cast<Store<NCT> *>(store);
+               if(!s)
+                       throw InvalidState("Type mismatch");
+               return s->data;
+       }
+
+       template<typename T>
+       bool check_type() const
+       {
+               return dynamic_cast<Store<typename RemoveConst<T>::Type> *>(store)!=0;
+       }
+
+       template<typename T>
+       operator T() const
+       { return value<T>(); }
+};
+
+} // namespace Msp
+
+#endif
diff --git a/source/debug/backtrace.cpp b/source/debug/backtrace.cpp
new file mode 100644 (file)
index 0000000..f43a9ed
--- /dev/null
@@ -0,0 +1,71 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+// Must include something to test for glibc
+#include <cstdlib>
+#if !defined(WIN32) && defined(__GLIBC__)
+#include <dlfcn.h>
+#include <execinfo.h>
+#endif
+#include "backtrace.h"
+#include "demangle.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Debug {
+
+Backtrace Backtrace::create()
+{
+#if !defined(WIN32) && defined(__GLIBC__)
+       void *addresses[50];
+       int count = ::backtrace(addresses, 50);
+
+       Backtrace bt;
+       Dl_info dli;
+       for(int i=0; i<count; ++i)
+       {
+               StackFrame frame;
+               frame.address = addresses[i];
+               if(dladdr(addresses[i], &dli))
+               {
+                       frame.file = dli.dli_fname;
+                       if(dli.dli_sname)
+                               frame.symbol = demangle(dli.dli_sname);
+               }
+               else
+                       frame.file = "<unknown>";
+               bt.frames.push_back(frame);
+       }
+
+       return bt;
+#else
+       return Backtrace();
+#endif
+}
+
+ostream &operator<<(ostream &out, const Backtrace &bt)
+{
+       const list<Backtrace::StackFrame> &frames = bt.get_frames();
+       for(list<Backtrace::StackFrame>::const_iterator i=frames.begin(); i!=frames.end(); ++i)
+               out<<*i<<'\n';
+
+       return out;
+}
+
+ostream &operator<<(ostream &out, const Backtrace::StackFrame &sf)
+{
+       out<<sf.address;
+       if(!sf.symbol.empty())
+               out<<" in "<<sf.symbol;
+       out<<" from "<<sf.file;
+
+       return out;
+}
+
+} // namespace Debug
+} // namespace Msp
diff --git a/source/debug/backtrace.h b/source/debug/backtrace.h
new file mode 100644 (file)
index 0000000..66e36f3
--- /dev/null
@@ -0,0 +1,43 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_DEBUG_BACKTRACE_H_
+#define MSP_DEBUG_BACKTRACE_H_
+
+#include <list>
+#include <ostream>
+#include <string>
+
+namespace Msp {
+namespace Debug {
+
+class Backtrace
+{
+public:
+       struct StackFrame
+       {
+               void *address;
+               std::string file;
+               std::string symbol;
+       };
+
+private:
+       std::list<StackFrame> frames;
+
+public:
+       const std::list<StackFrame> &get_frames() const { return frames; }
+
+       static Backtrace create();
+};
+
+std::ostream &operator<<(std::ostream &, const Backtrace &);
+std::ostream &operator<<(std::ostream &, const Backtrace::StackFrame &);
+
+} // namespace Debug
+} // namespace Msp
+
+#endif
diff --git a/source/debug/demangle.cpp b/source/debug/demangle.cpp
new file mode 100644 (file)
index 0000000..053fd52
--- /dev/null
@@ -0,0 +1,40 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#include <cstdlib>
+#ifdef __GNUC__
+#include <cxxabi.h>
+#endif
+#include "demangle.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Debug {
+
+string demangle(const string &sym)
+{
+#ifdef __GNUC__
+       int status;
+       char *dm = abi::__cxa_demangle(sym.c_str(), 0, 0, &status);
+       
+       string result;
+       if(status==0)
+               result = dm;
+       else
+               result = sym;
+       
+       free(dm);
+
+       return result;
+#else
+       return sym;
+#endif
+}
+
+} // namespace Debug
+} // namespace Msp
diff --git a/source/debug/demangle.h b/source/debug/demangle.h
new file mode 100644 (file)
index 0000000..f7c8f3b
--- /dev/null
@@ -0,0 +1,21 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_DEBUG_DEMANGLE_H_
+#define MSP_DEBUG_DEMANGLE_H_
+
+#include <string>
+
+namespace Msp {
+namespace Debug {
+
+std::string demangle(const std::string &);
+
+} // namespace Debug
+} // namespace Msp
+
+#endif
diff --git a/source/debug/profiler.cpp b/source/debug/profiler.cpp
new file mode 100644 (file)
index 0000000..ba69a61
--- /dev/null
@@ -0,0 +1,96 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2007  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#include "../core/except.h"
+#include "../time/units.h"
+#include "profiler.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Debug {
+
+Profiler::Profiler():
+       period(0),
+       inner(0)
+{ }
+
+void Profiler::set_period(unsigned p)
+{
+       if(p==period)
+               return;
+
+       period = p;
+       for(map<string, ScopeInfo>::iterator i=scopes.begin(); i!=scopes.end(); ++i)
+       {
+               ScopeInfo &si = i->second;
+               if(p==0)
+                       si.history.clear();
+               else
+                       si.history.assign(period, Time::zero);
+               si.hist_pos = 0;
+       }
+}
+
+void Profiler::add_scope(const std::string &name)
+{
+       if(!scopes.count(name))
+       {
+               map<string, ScopeInfo>::iterator i = scopes.insert(map<string, ScopeInfo>::value_type(name, ScopeInfo())).first;
+               i->second.history.resize(period);
+       }
+}
+
+ProfilingScope *Profiler::enter(ProfilingScope *ps)
+{
+       ProfilingScope *old = inner;
+       inner = ps;
+       return old;
+}
+
+void Profiler::record(const string &scope_name, const string &parent, const Time::TimeDelta &time, const Time::TimeDelta &child_t)
+{
+       map<string, ScopeInfo>::iterator i = scopes.find(scope_name);
+       if(i==scopes.end())
+       {
+               i = scopes.insert(map<string, ScopeInfo>::value_type(scope_name, ScopeInfo())).first;
+               i->second.history.resize(period);
+       }
+
+       ScopeInfo &si = i->second;
+       ++si.calls;
+       ++si.called_from[parent];
+       si.total_time += time;
+       si.self_time += time-child_t;
+       if(period)
+       {
+               si.avg_time += time/period-si.history[si.hist_pos]/period;
+               si.history[si.hist_pos++] = time;
+               if(si.hist_pos>=period)
+                       si.hist_pos -= period;
+       }
+       else
+               si.avg_time = si.total_time/si.calls;
+}
+
+const Profiler::ScopeInfo &Profiler::scope(const string &sn) const
+{
+       map<string, ScopeInfo>::const_iterator i = scopes.find(sn);
+       if(i==scopes.end())
+               throw KeyError("Unknown scope");
+
+       return i->second;
+}
+
+
+Profiler::ScopeInfo::ScopeInfo():
+       calls(0),
+       hist_pos(0)
+{ }
+
+} // namespace Debug
+} // namespace Msp
diff --git a/source/debug/profiler.h b/source/debug/profiler.h
new file mode 100644 (file)
index 0000000..f2d5812
--- /dev/null
@@ -0,0 +1,95 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2007  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_DEBUG_PROFILER_H_
+#define MSP_DEBUG_PROFILER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+#include "../time/timedelta.h"
+
+namespace Msp {
+namespace Debug {
+
+class ProfilingScope;
+
+/**
+A class for collecting timing data from a program.  It's not as efficient as
+external profilers, but allows profiling of custom scopes and retrieving the
+profiling data at run time.  An example usage could be showing realtime
+performance statistics of a game or a simulation.
+
+See also class ProfilingScope.
+
+Note: This is not thread-safe.  To profile multiple threads, create a separate
+Profiler for each thread.
+*/
+class Profiler
+{
+public:
+       struct ScopeInfo
+       {
+               unsigned calls;
+               Time::TimeDelta total_time;
+               Time::TimeDelta self_time;
+               Time::TimeDelta avg_time;
+               std::vector<Time::TimeDelta> history;
+               unsigned hist_pos;
+               std::map<std::string, unsigned> called_from;
+
+               ScopeInfo();
+       };
+
+private:
+       unsigned period;
+       std::map<std::string, ScopeInfo> scopes;
+       ProfilingScope *inner;
+
+public:
+       Profiler();
+
+       /**
+       Sets the averaging period for timing data, measured in calls.  Previous
+       average timings are cleared.
+       */
+       void set_period(unsigned p);
+
+       /**
+       Adds a scope without recording any calls to it.  Useful if you might need to
+       access a scope before it has finished for the first time.
+       */
+       void add_scope(const std::string &name);
+
+       /**
+       Changes the recorded innermost scope pointer and returns the old one.  This
+       is used by ProfilingScope to track child time and should not be called
+       manually.
+       */
+       ProfilingScope *enter(ProfilingScope *ps);
+
+       /**
+       Records a call to a scope.  You'll probably want to use a ProfilingScope
+       instead of calling this manually.
+
+       @param  sn  Scope name
+       @param  pn  Parent scope name
+       @param  t   Time spent in the scope
+       @param  ct  Time spent in child scopes
+       */
+       void record(const std::string &sn, const std::string &pn, const Time::TimeDelta &t, const Time::TimeDelta &ct);
+
+       /**
+       Returns informations about a scope.
+       */
+       const ScopeInfo &scope(const std::string &) const;
+};
+
+} // namespace Debug
+} // namespace Msp
+
+#endif
diff --git a/source/debug/profilingscope.cpp b/source/debug/profilingscope.cpp
new file mode 100644 (file)
index 0000000..3922234
--- /dev/null
@@ -0,0 +1,37 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2007  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#include "../time/utils.h"
+#include "profilingscope.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Debug {
+
+ProfilingScope::ProfilingScope(Profiler &p, const string &n):
+       profiler(p),
+       name(n),
+       parent(profiler.enter(this)),
+       start_t(Time::now())
+{ }
+
+ProfilingScope::~ProfilingScope()
+{
+       const Time::TimeDelta dt = Time::now()-start_t;
+       if(parent)
+       {
+               parent->child_t += dt;
+               profiler.record(name, parent->name, dt, child_t);
+       }
+       else
+               profiler.record(name, string(), dt, child_t);
+       profiler.enter(parent);
+}
+
+} // namespace Debug
+} // namespace Msp
diff --git a/source/debug/profilingscope.h b/source/debug/profilingscope.h
new file mode 100644 (file)
index 0000000..132c418
--- /dev/null
@@ -0,0 +1,41 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2007  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_DEBUG_PROFILINGSCOPE_H_
+#define MSP_DEBUG_PROFILINGSCOPE_H_
+
+#include "../time/timestamp.h"
+#include "profiler.h"
+
+namespace Msp {
+namespace Debug {
+
+/**
+RAII timing class to accompany Profiler.  Timing starts when an object is
+created and ends when it goes out of scope.  If there was another object in an
+outer scope, it is notified of the time used in inner scopes.
+*/
+class ProfilingScope
+{
+private:
+       Profiler &profiler;
+       std::string name;
+       ProfilingScope *parent;
+       Time::TimeStamp start_t;
+       Time::TimeDelta child_t;
+
+       ProfilingScope(const ProfilingScope &);
+       ProfilingScope &operator=(const ProfilingScope &);
+public:
+       ProfilingScope(Profiler &p, const std::string &n);
+       ~ProfilingScope();
+};
+
+} // namespace Debug
+} // namespace Msp
+
+#endif
diff --git a/source/time/datetime.cpp b/source/time/datetime.cpp
new file mode 100644 (file)
index 0000000..6617f86
--- /dev/null
@@ -0,0 +1,357 @@
+/* $Id$
+
+This file is part of libmspcore     
+Copyright © 2006  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#include <cstdlib>
+#include <sstream>
+#include <iomanip>
+#include "../core/except.h"
+#include "datetime.h"
+#include "timestamp.h"
+#include "units.h"
+
+using namespace std;
+
+namespace {
+
+inline bool is_leap_year(int y)
+{ return y%4==0 && (y%100 || y%400==0); }
+
+inline unsigned char month_days(int y, unsigned char m)
+{
+       switch(m)
+       {
+       case 4:
+       case 6:
+       case 9:
+       case 11:
+               return 30;
+       case 2:
+               return is_leap_year(y)?29:28;
+       default:
+               return 31;
+       }
+}
+
+template<typename T>
+inline int cmp_(T a, T b)
+{
+       if(a<b)
+               return -1;
+       if(a>b)
+               return 1;
+       return 0;
+}
+
+}
+
+namespace Msp {
+namespace Time {
+
+DateTime::DateTime(const TimeStamp &ts)
+{
+       init(ts);
+}
+
+DateTime::DateTime(const TimeStamp &ts, const TimeZone &tz)
+{
+       init(ts);
+       convert_timezone(tz);
+}
+
+DateTime::DateTime(int y, unsigned char m, unsigned char d)
+{
+       init(y, m, d, 0, 0, 0, 0);
+}
+
+DateTime::DateTime(int y, unsigned char m, unsigned char d, unsigned char h, unsigned char n, unsigned char s)
+{
+       init(y, m, d, h, n, s, 0);
+}
+
+DateTime::DateTime(int y, unsigned char m, unsigned char d, unsigned char h, unsigned char n, unsigned char s, unsigned u)
+{
+       init(y, m, d, h, n, s, u);
+}
+
+void DateTime::add_days(int days)
+{
+       int new_year = year;
+
+       /* Leap years have a 400 year cycle, so any 400 consecutive years have a
+       constant number of days (400*365+97 = 146097) */
+       new_year += days/146097*400;
+       days %= 146097;
+
+       if(days<0)
+       {
+               new_year -= 400;
+               days += 146097;
+       }
+
+       // Fudge factor for leap day
+       int fudge = (month<=2)?1:0;
+
+       // (Almost) every 4 year cycle has 1 leap year and 3 normal years
+       unsigned cycles = days/1461;
+       days %= 1461;
+
+       new_year += cycles*4;
+
+       // See how many non-leap-years we counted as leap years and reclaim the lost days
+       // XXX This breaks with negative years
+       unsigned missed_leap_days = ((year-fudge)%100+cycles*4)/100;
+       if((year-fudge)%400+cycles*4>=400)
+               --missed_leap_days;
+
+       days += missed_leap_days;
+
+       // Count single years from the 4 year cycle
+       cycles = days/365;
+       days %= 365;
+
+       new_year += cycles;
+
+       if((year-fudge)%4+cycles>=4)
+       {
+               // We passed a leap year - decrement days
+               if(days==0)
+               {
+                       days = is_leap_year(new_year-fudge)?365:364;
+                       --new_year;
+               }
+               else
+                       --days;
+       }
+
+       year = new_year;
+
+       // Step months
+       while(mday+days>month_days(year, month))
+       {
+               days -= month_days(year, month);
+               ++month;
+               if(month>12)
+               {
+                       ++year;
+                       month = 1;
+               }
+       }
+
+       mday += days;
+}
+
+void DateTime::set_timezone(const TimeZone &tz)
+{
+       zone = tz;
+}
+
+void DateTime::convert_timezone(const TimeZone &tz)
+{
+       add_raw((zone.get_offset()-tz.get_offset()).raw());
+       zone = tz;
+}
+
+DateTime DateTime::operator+(const TimeDelta &td) const
+{
+       DateTime dt(*this);
+       dt.add_raw(td.raw());
+       return dt;
+}
+
+DateTime &DateTime::operator+=(const TimeDelta &td)
+{
+       add_raw(td.raw());
+       return *this;
+}
+
+DateTime DateTime::operator-(const TimeDelta &td) const
+{
+       DateTime dt(*this);
+       dt.add_raw(-td.raw());
+       return dt;
+}
+
+DateTime &DateTime::operator-=(const TimeDelta &td)
+{
+       add_raw(-td.raw());
+       return *this;
+}
+
+int DateTime::cmp(const DateTime &dt) const
+{
+       if(int c = cmp_(year, dt.year))
+               return c;
+       if(int c = cmp_(month, dt.month))
+               return c;
+       if(int c = cmp_(mday, dt.mday))
+               return c;
+       if(int c = cmp_(hour, dt.hour))
+               return c;
+       if(int c = cmp_(minute, dt.minute))
+               return c;
+       if(int c = cmp_(second, dt.second))
+               return c;
+       if(int c = cmp_(usec, dt.usec))
+               return c;
+       return 0;
+}
+
+TimeStamp DateTime::get_timestamp() const
+{
+       if(year<-289701 || year>293641)
+               throw InvalidState("DateTime is not representable as a TimeStamp");
+
+       RawTime raw = (((hour*60LL)+minute)*60+second)*1000000+usec;
+       int days = (year-1970)*365;
+       days += (year-1)/4-(year-1)/100+(year-1)/400-477;
+       for(unsigned i=1; i<month; ++i)
+               days += month_days(year, i);
+       days += mday-1;
+
+       raw += days*86400000000LL;
+
+       return TimeStamp(raw);
+}
+
+string DateTime::format(const string &fmt) const
+{
+       ostringstream ss;
+       ss.fill('0');
+       for(string::const_iterator i=fmt.begin(); i!=fmt.end(); ++i)
+       {
+               if(*i=='%')
+               {
+                       ++i;
+                       if(i==fmt.end())
+                               break;
+                       else if(*i=='d')
+                               ss<<setw(2)<<int(mday);
+                       else if(*i=='H')
+                               ss<<setw(2)<<int(hour);
+                       else if(*i=='I')
+                               ss<<setw(2)<<hour%12;
+                       else if(*i=='m')
+                               ss<<setw(2)<<int(month);
+                       else if(*i=='M')
+                               ss<<setw(2)<<int(minute);
+                       else if(*i=='p')
+                               ss<<((hour>=12) ? "PM" : "AM");
+                       else if(*i=='S')
+                               ss<<setw(2)<<int(second);
+                       else if(*i=='y')
+                               ss<<setw(2)<<year%100;
+                       else if(*i=='Y')
+                               ss<<setw(4)<<internal<<year;
+                       else if(*i=='%')
+                               ss<<'%';
+               }
+               else
+                       ss<<*i;
+       }
+
+       return ss.str();
+}
+
+string DateTime::format_rfc3339() const
+{
+       string result = format("%Y-%m-%dT%H:%M:%S");
+       if(const TimeDelta &offs = zone.get_offset())
+       {
+               ostringstream ss;
+               ss.fill('0');
+               int m = abs(static_cast<int>(offs/Time::min));
+               ss<<(offs<zero ? '+' : '-')<<setw(2)<<m/60<<':'<<setw(2)<<m%60;
+               result += ss.str();
+       }
+       else
+               result += 'Z';
+       return result;
+}
+
+void DateTime::init(const TimeStamp &ts)
+{
+       year = 1970;
+       month = 1;
+       mday = 1;
+       hour = 0;
+       minute = 0;
+       second = 0;
+       usec = 0;
+       add_raw(ts.raw());
+}
+
+void DateTime::init(int y, unsigned char m, unsigned char d, unsigned char h, unsigned char n, unsigned char s, unsigned u)
+{
+       year = y;
+       month = m;
+       mday = d;
+       hour = h;
+       minute = n;
+       second = s;
+       usec = u;
+       validate();
+}
+
+void DateTime::add_raw(RawTime raw)
+{
+       int days = static_cast<int>(raw/86400000000LL);
+       raw %= 86400000000LL;
+       if(raw<0)
+       {
+               --days;
+               raw += 86400000000LL;
+       }
+
+       usec+=raw%1000000; raw /= 1000000;
+       second+=raw%60;    raw /= 60;
+       minute+=raw%60;    raw /= 60;
+       hour+=raw%24;      raw /= 24;
+
+       add_days(days);
+       normalize();
+}
+
+void DateTime::normalize()
+{
+       second += usec/1000000;
+       usec %= 1000000;
+       minute += second/60;
+       second %= 60;
+       hour += minute/60;
+       minute %= 60;
+       mday += hour/24;
+       hour %= 24;
+       while(mday>month_days(year, month))
+       {
+               mday -= month_days(year, month);
+               ++month;
+               if(month>12)
+               {
+                       ++year;
+                       month = 1;
+               }
+       }
+}
+
+void DateTime::validate() const
+{
+       if(usec>=1000000)
+               throw InvalidParameterValue("Microseconds out of range");
+       if(second>=60)
+               throw InvalidParameterValue("Seconds out of range");
+       if(minute>=60)
+               throw InvalidParameterValue("Minutes out of range");
+       if(hour>=24)
+               throw InvalidParameterValue("Hours out of range");
+       if(month<1 || month>12)
+               throw InvalidParameterValue("Month out of range");
+       if(mday<1 || mday>month_days(year, month))
+               throw InvalidParameterValue("Day of month out of range");
+}
+
+} // namespace Time
+} // namespace Msp
diff --git a/source/time/datetime.h b/source/time/datetime.h
new file mode 100644 (file)
index 0000000..7c2a349
--- /dev/null
@@ -0,0 +1,91 @@
+/* $Id$
+
+This file is part of libmspcore     
+Copyright © 2006  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_TIME_DATETIME_H_
+#define MSP_TIME_DATETIME_H_
+
+#include <string>
+#include "timezone.h"
+#include "types.h"
+
+namespace Msp {
+namespace Time {
+
+class TimeDelta;
+class TimeStamp;
+
+/**
+Provides handling of arbitary dates and times.  Can represent a moment of time
+in the range of about ±2.1×10⁹ years.  It can also be formatted into a string
+for presentation to the user.
+
+Due to the complex internal representation, arithmetic operations on a DateTime
+are relatively slow.  For purposes of internal scheduling in a program, a
+TimeStamp is a better choice.
+*/
+class DateTime
+{
+private:
+       int           year;
+       unsigned char month;
+       unsigned char mday;
+       unsigned char hour;
+       unsigned char minute;
+       unsigned char second;
+       unsigned      usec;
+       TimeZone      zone;
+
+public:
+       DateTime(const TimeStamp &);
+       DateTime(const TimeStamp &, const TimeZone &);
+       DateTime(int, unsigned char, unsigned char);
+       DateTime(int, unsigned char, unsigned char, unsigned char, unsigned char, unsigned char);
+       DateTime(int, unsigned char, unsigned char, unsigned char, unsigned char, unsigned char, unsigned);
+private:
+       void init(const TimeStamp &);
+       void init(int, unsigned char, unsigned char, unsigned char, unsigned char, unsigned char, unsigned);
+
+public:
+       int           get_year() const   { return year; }
+       unsigned char get_month() const  { return month; }
+       unsigned char get_mday() const   { return mday; }
+       unsigned char get_hour() const   { return hour; }
+       unsigned char get_minute() const { return minute; }
+       unsigned char get_second() const { return second; }
+       unsigned      get_usec() const   { return usec; }
+
+       void add_days(int);
+       void set_timezone(const TimeZone &);
+       void convert_timezone(const TimeZone &);
+
+       DateTime operator+(const TimeDelta &) const;
+       DateTime &operator+=(const TimeDelta &);
+       DateTime operator-(const TimeDelta &) const;
+       DateTime &operator-=(const TimeDelta &);
+
+       bool operator==(const DateTime &d) const { return cmp(d)==0; }
+       bool operator!=(const DateTime &d) const { return cmp(d)!=0; }
+       bool operator<(const DateTime &d) const { return cmp(d)<0; }
+       bool operator<=(const DateTime &d) const { return cmp(d)<=0; }
+       bool operator>(const DateTime &d) const { return cmp(d)>0; }
+       bool operator>=(const DateTime &d) const { return cmp(d)>=0; }
+
+       int cmp(const DateTime &) const;
+       TimeStamp get_timestamp() const;
+       std::string format(const std::string &) const;
+       std::string format_rfc3339() const;
+
+private:
+       void add_raw(RawTime);
+       void normalize();
+       void validate() const;
+};
+
+} // namespace Time
+} // namespace Msp
+
+#endif
diff --git a/source/time/timedelta.cpp b/source/time/timedelta.cpp
new file mode 100644 (file)
index 0000000..c74ffce
--- /dev/null
@@ -0,0 +1,87 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2006  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#include <sstream>
+#include <iomanip>
+#include "timedelta.h"
+#include "units.h"
+
+using namespace std;
+
+namespace {
+
+using Msp::Time::RawTime;
+
+void print_part(ostream &out, RawTime &value, RawTime unit, char sep, bool &first)
+{
+       if(value<unit && first)
+               return;
+       
+       if(!first)
+               out<<sep<<setw(2);
+       
+       out<<value/unit;
+       value %= unit;
+       first = false;
+}
+
+}
+
+namespace Msp {
+namespace Time {
+
+ostream &operator<<(ostream &out, const TimeDelta &td)
+{
+       ostringstream ss;
+       ss.fill('0');
+
+       RawTime value = td.raw();
+
+       if(value<0)
+       {
+               ss<<'-';
+               value = -value;
+       }
+
+       if(value==0)
+               ss<<'0';
+       else if(value<1000)
+               ss<<value<<"µs";
+       else if(value<1000000)
+       {
+               ss<<value/1000;
+               value %= 1000;
+               if(value)
+                       ss<<'.'<<setw(3)<<value;
+               ss<<"ms";
+       }
+       else
+       {
+               bool first = true;
+               print_part(ss, value, 86400000000LL,  0,  first);
+               print_part(ss, value, 3600000000LL,  '-', first);
+               print_part(ss, value, 60000000LL,    ':', first);
+               print_part(ss, value, 1000000LL,     ':', first);
+
+               if(value)
+               {
+                       ss<<'.';
+                       if(value%1000)
+                               ss<<setw(6)<<value;
+                       else
+                               ss<<setw(3)<<value/1000;
+               }
+               ss<<"s";
+       }
+
+       out<<ss.str();
+
+       return out;
+}
+
+} // namespace Time
+} // namespace Msp
diff --git a/source/time/timedelta.h b/source/time/timedelta.h
new file mode 100644 (file)
index 0000000..0ced98e
--- /dev/null
@@ -0,0 +1,91 @@
+/* $Id$
+
+This file is part of libmspcore     
+Copyright © 2006  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_TIME_TIMEDELTA_H_
+#define MSP_TIME_TIMEDELTA_H_
+
+#include <time.h>
+#include <sys/time.h>
+#include <ostream>
+#include "types.h"
+
+namespace Msp {
+namespace Time {
+
+/**
+Represents a quantity of time, such as five seconds.
+*/
+class TimeDelta
+{
+private:
+       RawTime usec;
+
+public:
+       /**
+       Constructs a zero TimeDelta.
+       */
+       TimeDelta(): usec(0) { }
+
+       /**
+       Constructs a TimeDelta from a plain number.  The purpose of this is to allow
+       serialization together with the raw() function.  For creating TimeDeltas
+       with a specific length, see units.h.
+       */
+       explicit TimeDelta(RawTime u): usec(u) { }
+
+       /**
+       Returns the raw number stored inside the TimeDelta.  This should only be used
+       for serialization and the result should not be interpreted in any way.
+       */
+       RawTime raw() const { return usec; }
+
+#ifndef WIN32
+       /**
+       Fills in a timespec struct.  To get a meaningful scalar value from the
+       TimeDelta, divide with one of the values in units.h.
+       */
+       void fill_timespec(timespec &ts) const { ts.tv_sec=usec/1000000; ts.tv_nsec = (usec%1000000)*1000; }
+
+       void fill_timeval(timeval &tv) const { tv.tv_sec=usec/1000000; tv.tv_usec = usec%1000000; }
+#endif
+
+       TimeDelta operator+(const TimeDelta &t) const  { return TimeDelta(usec+t.usec); }
+       TimeDelta &operator+=(const TimeDelta &t)      { usec+=t.usec; return *this; }
+       TimeDelta operator-(const TimeDelta &t) const  { return TimeDelta(usec-t.usec); }
+       TimeDelta &operator-=(const TimeDelta &t)      { usec-=t.usec; return *this; }
+
+       template<typename T>
+       TimeDelta operator*(T a) const                 { return TimeDelta(RawTime(usec*a)); }
+       template<typename T>
+       TimeDelta &operator*=(T a)                     { usec=RawTime(usec*a); return *this; }
+
+       template<typename T>
+       TimeDelta operator/(T a) const                 { return TimeDelta(RawTime(usec/a)); }
+       template<typename T>
+       TimeDelta &operator/=(T a)                     { usec=RawTime(usec/a); return *this; }
+
+       double    operator/(const TimeDelta &t) const  { return double(usec)/t.usec; }
+
+       bool      operator>(const TimeDelta &t) const  { return usec>t.usec; }
+       bool      operator>=(const TimeDelta &t) const { return usec>=t.usec; }
+       bool      operator<(const TimeDelta &t) const  { return usec<t.usec; }
+       bool      operator<=(const TimeDelta &t) const { return usec<=t.usec; }
+       bool      operator==(const TimeDelta &t) const { return usec==t.usec; }
+       bool      operator!=(const TimeDelta &t) const { return usec!=t.usec; }
+
+       operator const void *() const                  { return usec ? this : 0; }
+};
+
+template<typename T>
+inline TimeDelta operator*(T a, const TimeDelta &t)   { return t*a; }
+
+extern std::ostream &operator<<(std::ostream &, const TimeDelta &);
+
+} // namespace Time
+} // namespace Msp
+
+#endif
diff --git a/source/time/timer.cpp b/source/time/timer.cpp
new file mode 100644 (file)
index 0000000..ab53059
--- /dev/null
@@ -0,0 +1,143 @@
+/* $Id$
+
+This file is part of libmspcore     
+Copyright © 2006, 2009  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#include <algorithm>
+#include "timer.h"
+#include "utils.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Time {
+
+Timer::~Timer()
+{
+       for(vector<SlotProxy>::iterator i=slots.begin(); i!=slots.end(); ++i)
+               delete i->slot;
+}
+
+Timer::Slot &Timer::add(const TimeDelta &td)
+{
+       Slot *s = new Slot(td);
+       mutex.lock();
+       slots.push_back(s);
+       push_heap(slots.begin(), slots.end());
+       mutex.unlock();
+       sem.signal();
+       return *s;
+}
+
+Timer::Slot &Timer::add(const TimeStamp &ts)
+{
+       Slot *s = new Slot(ts);
+       {
+               MutexLock l(mutex);
+               slots.push_back(s);
+               push_heap(slots.begin(), slots.end());
+       }
+       sem.signal();
+       return *s;
+}
+
+void Timer::cancel(Slot &slot)
+{
+       MutexLock l(mutex);
+       for(vector<SlotProxy>::iterator i=slots.begin(); i!=slots.end(); ++i)
+               if(i->slot==&slot)
+               {
+                       delete i->slot;
+                       slots.erase(i);
+                       make_heap(slots.begin(), slots.end());
+                       return;
+               }
+}
+
+void Timer::tick(bool block)
+{
+       Slot *next = 0;
+       {
+               MutexLock l(mutex);
+               while(1)
+               {
+                       if(slots.empty())
+                       {
+                               if(block)
+                                       sem.wait();
+                               else
+                                       return;
+                       }
+
+                       next = slots.begin()->slot;
+                       const TimeStamp &stamp = next->get_timeout();
+                       const TimeStamp t = now();
+                       if(stamp<=t)
+                               break;
+                       else if(block)
+                               sem.wait(stamp-t);
+                       else
+                               return;
+               }
+
+               pop_heap(slots.begin(), slots.end());
+               slots.pop_back();
+       }
+
+       try
+       {
+               if(next->signal_timeout.emit() && next->increment())
+               {
+                       MutexLock l(mutex);
+                       slots.push_back(next);
+                       push_heap(slots.begin(), slots.end());
+               }
+               else
+                       delete next;
+       }
+       catch(...)
+       {
+               delete next;
+               throw;
+       }
+}
+
+TimeStamp Timer::get_next_timeout() const
+{
+       if(slots.empty())
+               return TimeStamp();
+       return slots.begin()->slot->get_timeout();
+}
+
+
+Timer::Slot::Slot(const TimeDelta &td):
+       interval(td),
+       timeout(now()+interval)
+{ }
+
+Timer::Slot::Slot(const TimeStamp &ts):
+       timeout(ts)
+{ }
+
+bool Timer::Slot::increment()
+{
+       if(!interval)
+               return false;
+       timeout += interval;
+       return true;
+}
+
+
+Timer::SlotProxy::SlotProxy(Slot *s):
+       slot(s)
+{ }
+
+bool Timer::SlotProxy::operator<(const SlotProxy &sp) const
+{
+       return slot->get_timeout()>sp.slot->get_timeout();
+}
+
+} // namespace Time
+} // namespace Msp
diff --git a/source/time/timer.h b/source/time/timer.h
new file mode 100644 (file)
index 0000000..26ebf64
--- /dev/null
@@ -0,0 +1,95 @@
+/* $Id$
+
+This file is part of libmspcore     
+Copyright © 2006, 2009  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_TIME_TIMER_H_
+#define MSP_TIME_TIMER_H_
+
+#include <vector>
+#include <sigc++/sigc++.h>
+#include "../core/mutex.h"
+#include "../core/semaphore.h"
+#include "timedelta.h"
+#include "timestamp.h"
+
+namespace Msp {
+namespace Time {
+
+/**
+A class for executing functions in a deferred or periodical fashion.  The add a
+timer, use one of the add functions and connect a functor to the timeout signal
+of the returned slot.
+
+This class is thread-safe, to allow running timers in a separate thread.
+*/
+class Timer
+{
+public:
+       class Slot
+       {
+       public:
+               sigc::signal<bool> signal_timeout;
+       
+       private:
+               TimeDelta interval;
+               TimeStamp timeout;
+       
+       public:
+               Slot(const TimeDelta &);
+               Slot(const TimeStamp &);
+               const TimeStamp &get_timeout() const { return timeout; }
+               bool increment();
+       };
+
+private:
+       struct SlotProxy
+       {
+               Slot *slot;
+
+               SlotProxy(Slot *);
+               bool operator<(const SlotProxy &) const;
+       };
+
+       std::vector<SlotProxy> slots;
+       Semaphore sem;
+       Mutex mutex;
+
+public:
+       ~Timer();
+
+       /**
+       Adds a timer that will be executed periodically as long as the timeout
+       signal hander returns true.
+       */
+       Slot &add(const TimeDelta &);
+
+       /**
+       Adds a timer that will be executed once at a specific time.  The return
+       value of the timeout signal handler is ignored.
+       */
+       Slot &add(const TimeStamp &);
+
+       /**
+       Cancels a previously added timer.
+       */
+       void cancel(Slot &);
+
+       /**
+       Checks all timers, executing any that have timed out.  If block is true,
+       waits until one times out.
+
+       Note: If there are no active timers when a blocking tick is executed, it
+       won't return until a timer is added from another thread.
+       */
+       void tick(bool block = true);
+
+       TimeStamp get_next_timeout() const;
+};
+
+} // namespace Time
+} // namespace Msp
+
+#endif
diff --git a/source/time/timestamp.h b/source/time/timestamp.h
new file mode 100644 (file)
index 0000000..64309ce
--- /dev/null
@@ -0,0 +1,70 @@
+/* $Id$
+
+This file is part of libmspcore     
+Copyright © 2006, 2009  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_TIME_TIMESTAMP_H_
+#define MSP_TIME_TIMESTAMP_H_
+
+#include "timedelta.h"
+#include "types.h"
+
+namespace Msp {
+namespace Time {
+
+/**
+Represents a moment in time.  The main source of TimeStamps is the now()
+function.
+
+For representing user-specified times, use the DateTime class.
+*/
+class TimeStamp
+{
+private:
+       RawTime usec;
+
+public:
+       /**
+       Construct a TimeStamp that represents an arbitarily distant point in the
+       past.  It's guaranteed to be less than any valid timestamp.
+       */
+       TimeStamp(): usec(0) { }
+
+       /**
+       Constructs a TimeStamp from a plain number.  The purpose of this is to allow
+       serialization together with the raw() function.
+       */
+       explicit TimeStamp(RawTime u): usec(u) { }
+
+       /**
+       Returns the raw number stored inside the TimeStamp.  This value should be
+       considered opaque and only be used for serialization.
+       */
+       RawTime raw() const { return usec; }
+
+       time_t to_unixtime() const { return usec/1000000LL; }
+
+       TimeStamp operator+(const TimeDelta &t) const  { return TimeStamp(usec+t.raw()); }
+       TimeStamp &operator+=(const TimeDelta &t)      { usec+=t.raw(); return *this; }
+       TimeStamp operator-(const TimeDelta &t) const  { return TimeStamp(usec-t.raw()); }
+       TimeStamp &operator-=(const TimeDelta &t)      { usec-=t.raw(); return *this; }
+       TimeDelta operator-(const TimeStamp &t) const  { return TimeDelta(usec-t.usec); }
+
+       bool      operator>=(const TimeStamp &t) const { return usec>=t.usec; }
+       bool      operator>(const TimeStamp &t) const  { return usec>t.usec; }
+       bool      operator<=(const TimeStamp &t) const { return usec<=t.usec; }
+       bool      operator<(const TimeStamp &t) const  { return usec<t.usec; }
+       bool      operator==(const TimeStamp &t) const { return usec==t.usec; }
+       bool      operator!=(const TimeStamp &t) const { return usec!=t.usec; }
+
+       operator const void *() const                  { return usec>0 ? this : 0; }
+
+       static TimeStamp from_unixtime(time_t t) { return TimeStamp(t*1000000LL); }
+};
+
+} // namespace Time
+} // namespace Msp
+
+#endif
diff --git a/source/time/timezone.cpp b/source/time/timezone.cpp
new file mode 100644 (file)
index 0000000..9c14624
--- /dev/null
@@ -0,0 +1,154 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2008-2009  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#include <cstdlib>
+#include <sstream>
+#include <iomanip>
+#ifdef WIN32
+#include <windows.h>
+#else
+#include <fcntl.h>
+#endif
+#include "../core/except.h"
+#include "timestamp.h"
+#include "timezone.h"
+#include "units.h"
+#include "utils.h"
+
+using namespace std;
+
+namespace {
+
+using Msp::Time::TimeZone;
+
+#ifndef WIN32
+long get_long(char *&ptr)
+{
+       long result = 0;
+       for(unsigned i=0; i<4; ++i)
+               result = (result<<8)+static_cast<unsigned char >(*ptr++);
+       return result;
+}
+#endif
+
+TimeZone get_local_timezone()
+{
+#ifdef WIN32
+       TIME_ZONE_INFORMATION tzinfo;
+       DWORD dst = GetTimeZoneInformation(&tzinfo);
+       if(dst==TIME_ZONE_ID_INVALID)
+               throw Msp::SystemError("Failed to get time zone information", GetLastError());
+
+       int offset = tzinfo.Bias;
+       if(dst==TIME_ZONE_ID_STANDARD)
+               offset += tzinfo.StandardBias;
+       else if(dst==TIME_ZONE_ID_DAYLIGHT)
+               offset += tzinfo.DaylightBias;
+
+       return TimeZone(offset);
+#else
+       int fd = open("/etc/localtime", O_RDONLY);
+       if(fd>=-1)
+       {
+               char hdr[44];
+               int len = read(fd, hdr, sizeof(hdr));
+               long gmtoff = -1;
+               string name;
+               if(len==44 && hdr[0]=='T' && hdr[1]=='Z' && hdr[2]=='i' && hdr[3]=='f')
+               {
+                       char *ptr = hdr+20;
+                       long isgmtcnt = get_long(ptr);
+                       long isstdcnt = get_long(ptr);
+                       long leapcnt = get_long(ptr);
+                       long timecnt = get_long(ptr);
+                       long typecnt = get_long(ptr);
+                       long charcnt = get_long(ptr);
+                       int size = timecnt*5+typecnt*6+isgmtcnt+isstdcnt+leapcnt*8+charcnt;
+                       char buf[size];
+                       len = read(fd, buf, size);
+                       if(len==size)
+                       {
+                               ptr = buf;
+                               int index = -1;
+                               time_t cur_time = Msp::Time::now().to_unixtime();
+                               for(int i=0; i<timecnt; ++i)
+                                       if(get_long(ptr)<=cur_time)
+                                               index = i;
+
+                               if(index>0)
+                                       index = ptr[index];
+                               ptr += timecnt;
+
+                               int abbrind = 0;
+                               for(int i=0; i<typecnt; ++i)
+                               {
+                                       if((index>=0 && i==index) || (index<0 && !ptr[4] && gmtoff==-1))
+                                       {
+                                               gmtoff = get_long(ptr);
+                                               ++ptr;
+                                               abbrind = *ptr++;
+                                       }
+                                       else
+                                               ptr += 6;
+                               }
+
+                               name = ptr+abbrind;
+                       }
+               }
+               close(fd);
+
+               if(gmtoff!=-1)
+                       return TimeZone(-gmtoff/60, name);
+       }
+       return TimeZone();
+#endif
+}
+
+}
+
+namespace Msp {
+namespace Time {
+
+TimeZone::TimeZone():
+       name("UTC")
+{ }
+
+TimeZone::TimeZone(int minutes_west):
+       offset(minutes_west*min)
+{
+       if(minutes_west)
+       {
+               ostringstream ss;
+               ss.fill('0');
+               int m = abs(minutes_west);
+               ss<<"UTC"<<(minutes_west<0 ? '-' : '+')<<m/60;
+               if(m%60)
+                       ss<<':'<<setw(2)<<m%60;
+       }
+       else
+               name = "UTC";
+}
+
+TimeZone::TimeZone(int minutes_west, const string &n):
+       name(n),
+       offset(minutes_west*min)
+{ }
+
+const TimeZone &TimeZone::utc()
+{
+       static TimeZone tz(0);
+       return tz;
+}
+
+const TimeZone &TimeZone::local()
+{
+       static TimeZone tz = get_local_timezone();
+       return tz;
+}
+
+} // namespace Time
+} // namespace Msp
diff --git a/source/time/timezone.h b/source/time/timezone.h
new file mode 100644 (file)
index 0000000..1a71dd8
--- /dev/null
@@ -0,0 +1,37 @@
+/* $Id$
+
+This file is part of libmspcore
+Copyright © 2008-2009  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_TIME_TIMEZONE_H_
+#define MSP_TIME_TIMEZONE_H_
+
+#include "timedelta.h"
+
+namespace Msp {
+namespace Time {
+
+class TimeZone
+{
+private:
+       std::string name;
+       TimeDelta offset;
+
+public:
+       TimeZone();
+       TimeZone(int);
+       TimeZone(int, const std::string &);
+       
+       const std::string &get_name() const { return name; }
+       const TimeDelta &get_offset() const { return offset; }
+
+       static const TimeZone &utc();
+       static const TimeZone &local();
+};
+
+} // namespace Time
+} // namespace Msp
+
+#endif
diff --git a/source/time/types.h b/source/time/types.h
new file mode 100644 (file)
index 0000000..89c7405
--- /dev/null
@@ -0,0 +1,27 @@
+/* $Id$
+
+This file is part of libmspcore     
+Copyright © 2006,2008  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_TIME_TYPES_H_
+#define MSP_TIME_TYPES_H_
+
+#ifndef WIN32
+#include <stdint.h>
+#endif
+
+namespace Msp {
+namespace Time {
+
+#ifdef WIN32
+typedef __int64 RawTime;
+#else
+typedef int64_t RawTime;
+#endif
+
+} // namespace Time
+} // namespace Msp
+
+#endif
diff --git a/source/time/units.cpp b/source/time/units.cpp
new file mode 100644 (file)
index 0000000..9ea2840
--- /dev/null
@@ -0,0 +1,24 @@
+/* $Id$
+
+This file is part of libmspcore     
+Copyright © 2006  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#include "units.h"
+
+namespace Msp {
+namespace Time {
+
+// Constants to be used in creation of TimeDeltas
+const TimeDelta zero(0);
+const TimeDelta usec(1);
+const TimeDelta msec(1000);
+const TimeDelta sec(1000000);
+const TimeDelta min(60*1000000);
+const TimeDelta hour(3600*1000000LL);
+const TimeDelta day(86400*1000000LL);
+const TimeDelta week(7*86400*1000000LL);
+
+} // namespace Time
+} // namespace Msp
diff --git a/source/time/units.h b/source/time/units.h
new file mode 100644 (file)
index 0000000..a0ed2b5
--- /dev/null
@@ -0,0 +1,28 @@
+/* $Id$
+
+This file is part of libmspcore     
+Copyright © 2006  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_TIME_UNITS_H_
+#define MSP_TIME_UNITS_H_
+
+#include "timedelta.h"
+
+namespace Msp {
+namespace Time {
+
+extern const TimeDelta zero;
+extern const TimeDelta usec;
+extern const TimeDelta msec;
+extern const TimeDelta sec;
+extern const TimeDelta min;
+extern const TimeDelta hour;
+extern const TimeDelta day;
+extern const TimeDelta week;
+
+} // namespace Time
+} // namespace Msp
+
+#endif
diff --git a/source/time/utils.cpp b/source/time/utils.cpp
new file mode 100644 (file)
index 0000000..1d0b8d0
--- /dev/null
@@ -0,0 +1,94 @@
+/* $Id$
+
+This file is part of libmspcore     
+Copyright © 2006-2008  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifdef WIN32
+#include <windows.h>
+#else
+#include <sys/resource.h>
+#include <sys/time.h>
+#endif
+#include "datetime.h"
+#include "timedelta.h"
+#include "timestamp.h"
+#include "units.h"
+#include "utils.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Time {
+
+/**
+Returns the current timestamp.
+*/
+TimeStamp now()
+{
+#ifndef WIN32
+       timeval tv;
+       gettimeofday(&tv, 0);
+       return TimeStamp(tv.tv_sec*1000000LL+tv.tv_usec);
+#else
+       static RawTime epoch = 0;
+       if(!epoch)
+       {
+               SYSTEMTIME st;
+               st.wYear = 1970;
+               st.wMonth = 1;
+               st.wDay = 1;
+               st.wHour = 0;
+               st.wMinute = 0;
+               st.wSecond = 0;
+               st.wMilliseconds = 0;
+
+               FILETIME ft;
+               SystemTimeToFileTime(&st, &ft);
+               epoch = (ft.dwLowDateTime+(static_cast<RawTime>(ft.dwHighDateTime)<<32))/10;
+       }
+       
+       FILETIME ft;
+       GetSystemTimeAsFileTime(&ft);
+       return TimeStamp((ft.dwLowDateTime+(static_cast<RawTime>(ft.dwHighDateTime)<<32))/10-epoch);
+#endif
+}
+
+string format_now(const string &fmt)
+{
+       return DateTime(now()).format(fmt);
+}
+
+/**
+Returns the CPU time used by the program so far.
+*/
+TimeDelta get_cpu_time()
+{
+#ifndef WIN32
+       rusage ru;
+       getrusage(RUSAGE_SELF, &ru);
+       return (ru.ru_utime.tv_sec+ru.ru_stime.tv_sec)*sec + (ru.ru_utime.tv_usec+ru.ru_stime.tv_usec)*usec;
+#else
+       //XXX Figure out the function to use on Win32
+       return TimeDelta();
+#endif
+}
+
+/**
+Sleeps for the given time.
+*/
+int sleep(const TimeDelta &d)
+{
+#ifndef WIN32
+       timespec ts;
+       d.fill_timespec(ts);
+       return nanosleep(&ts, 0);
+#else
+       Sleep((DWORD)(d/msec));
+       return 0;
+#endif
+}
+
+} // namespace Time
+} // namespace Msp
diff --git a/source/time/utils.h b/source/time/utils.h
new file mode 100644 (file)
index 0000000..9755356
--- /dev/null
@@ -0,0 +1,27 @@
+/* $Id$
+
+This file is part of libmspcore     
+Copyright © 2006-2007  Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+
+#ifndef MSP_TIME_UTILS_H_
+#define MSP_TIME_UTILS_H_
+
+#include <string>
+
+namespace Msp {
+namespace Time {
+
+class TimeDelta;
+class TimeStamp;
+
+extern TimeStamp now();
+extern std::string format_now(const std::string &);
+extern TimeDelta get_cpu_time();
+extern int sleep(const TimeDelta &);
+
+} // namespace Time
+} // namespace Msp
+
+#endif