From: Mikko Rasa Date: Wed, 25 May 2011 13:46:08 +0000 (+0300) Subject: Merge branch 'strings-master' X-Git-Url: http://git.tdb.fi/?p=libs%2Fcore.git;a=commitdiff_plain;h=b56eb5ec1da675da0c66abc53c1e4f6c4e4cccbd;hp=b42ed73a1b241c0e93ee03c43c4584b41c549bac Merge branch 'strings-master' Conflicts: .gitignore Build Changelog.txt --- diff --git a/.gitignore b/.gitignore index 6191adc..82b1d62 100644 --- a/.gitignore +++ b/.gitignore @@ -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 06545dc..e1f3fd7 100644 --- 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"; }; }; diff --git a/Changelog.txt b/Changelog.txt index 9af98cd..a9d3619 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -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 index 0000000..139f94f --- /dev/null +++ b/source/core/application.cpp @@ -0,0 +1,161 @@ +/* $Id$ + +This file is part of libmspcore +Copyright © 2006-2008, 2011 Mikko Rasa, Mikkosoft Productions +Distributed under the LGPL +*/ + +#include +#include +#include +#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: "<(&e); + if(exc && !exc->get_backtrace().get_frames().empty()) + { + cerr<<" backtrace:\n"; + const list &frames = exc->get_backtrace().get_frames(); + for(list::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: "<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 index 0000000..b51ac3c --- /dev/null +++ b/source/core/application.h @@ -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. +*/ +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 +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 RegisteredApplication::Starter RegisteredApplication::starter_; + +} // namespace Msp + +#endif diff --git a/source/core/except.cpp b/source/core/except.cpp new file mode 100644 index 0000000..bdcdd7d --- /dev/null +++ b/source/core/except.cpp @@ -0,0 +1,62 @@ +/* $Id$ + +This file is part of libmspcore +Copyright © 2006-2008 Mikko Rasa, Mikkosoft Productions +Distributed under the LGPL +*/ + +#include +#include +#ifdef WIN32 +#include +#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< +#include +#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 +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 index 0000000..74f1476 --- /dev/null +++ b/source/core/getopt.cpp @@ -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::iterator i=opts.begin(); i!=opts.end(); ++i) + delete *i; +} + +GetOpt::OptBase &GetOpt::get_option(char s) +{ + for(list::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::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::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::const_iterator i=opts.begin(); (!any_short && i!=opts.end()); ++i) + any_short = (*i)->get_short(); + + string::size_type maxw = 0; + list switches; + for(list::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::const_iterator j = switches.begin(); + for(list::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 index 0000000..8e0f49a --- /dev/null +++ b/source/core/getopt.h @@ -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 +#include +#include +#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 + 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 + 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 opts; + std::vector args; + +public: + ~GetOpt(); + + const std::vector &get_args() const { return args; } + + template + OptBase &add_option(char s, const std::string &l, T &d, ArgType a = NO_ARG) + { opts.push_back(new Option(s, l, d, a)); return *opts.back(); } + + template + OptBase &add_option(char s, const std::string &l, std::list &d, ArgType a = REQUIRED_ARG) + { opts.push_back(new ListOption >(s, l, d, a)); return *opts.back(); } + + template + 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::store() { data = true; } +template<> inline void GetOpt::Option::store() { ++data; } + +template<> inline void GetOpt::Option::store(const std::string &a) +{ data = a; } + +template<> inline void GetOpt::ListOption >::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 index 0000000..f73ef81 --- /dev/null +++ b/source/core/main.cpp @@ -0,0 +1,24 @@ +/* $Id$ + +This file is part of libmspcore +Copyright © 2006 Mikko Rasa, Mikkosoft Productions +Distributed under the LGPL +*/ + +#ifdef WIN32 +#include +#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 index 0000000..f6dff16 --- /dev/null +++ b/source/core/meta.h @@ -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 +struct RemoveConst +{ typedef T Type; }; + +template +struct RemoveConst +{ typedef T Type; }; + +template +struct RemoveReference +{ typedef T Type; }; + +template +struct RemoveReference +{ typedef T Type; }; + +} // namespace Msp + +#endif diff --git a/source/core/mutex.h b/source/core/mutex.h new file mode 100644 index 0000000..c0c2682 --- /dev/null +++ b/source/core/mutex.h @@ -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 +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 mutex; + T *data; +}; + +/*template +class MutexPtr: public RefCount +{ +public: + MutexPtr(T *d, Mutex &m): mutex(m), data(d) { mutex.lock(); } + MutexPtr(const MutexPtr &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 index 0000000..20450f1 --- /dev/null +++ b/source/core/refptr.h @@ -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 +class RefPtr +{ + template 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 + RefPtr(const RefPtr &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 + RefPtr &operator=(const RefPtr &p) { return assign(p); } + +private: + template + RefPtr &assign(const RefPtr &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 + static RefPtr cast_dynamic(const RefPtr &p) + { return RefPtr(dynamic_cast(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 index 0000000..139cae2 --- /dev/null +++ b/source/core/semaphore.cpp @@ -0,0 +1,122 @@ +/* $Id$ + +This file is part of libmspcore +Copyright © 2006 Mikko Rasa, Mikkosoft Productions +Distributed under the LGPL +*/ + +#ifndef WIN32 +#include +#endif +#include +#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 index 0000000..e6c09c5 --- /dev/null +++ b/source/core/semaphore.h @@ -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 index 0000000..810b1f8 --- /dev/null +++ b/source/core/thread.cpp @@ -0,0 +1,77 @@ +/* $Id$ + +This file is part of libmspcore +Copyright © 2006 Mikko Rasa, Mikkosoft Productions +Distributed under the LGPL +*/ + +#ifndef WIN32 +#include +#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 index 0000000..92923f3 --- /dev/null +++ b/source/core/thread.h @@ -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(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 index 0000000..1bd865d --- /dev/null +++ b/source/core/types.h @@ -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 +#else +#include +#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 index 0000000..8d6db9a --- /dev/null +++ b/source/core/variant.h @@ -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 + struct Store: public StoreBase + { + T data; + + Store(T d): data(d) { } + virtual StoreBase *clone() const { return new Store(data); } + }; + + StoreBase *store; + +public: + Variant(): store(0) { } + template + Variant(const T &v): store(new Store::Type>(v)) { } + Variant(const Variant &v): store(v.store ? v.store->clone() : 0) { } + ~Variant() { delete store; } + + template + Variant &operator=(const T &v) + { + delete store; + store = new Store::Type>(v); + return *this; + } + + Variant &operator=(const Variant &v) + { + delete store; + store = (v.store ? v.store->clone() : 0); + return *this; + } + + template + T &value() const + { + typedef typename RemoveConst::Type NCT; + Store *s = dynamic_cast *>(store); + if(!s) + throw InvalidState("Type mismatch"); + return s->data; + } + + template + bool check_type() const + { + return dynamic_cast::Type> *>(store)!=0; + } + + template + operator T() const + { return value(); } +}; + +} // namespace Msp + +#endif diff --git a/source/debug/backtrace.cpp b/source/debug/backtrace.cpp new file mode 100644 index 0000000..f43a9ed --- /dev/null +++ b/source/debug/backtrace.cpp @@ -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 +#if !defined(WIN32) && defined(__GLIBC__) +#include +#include +#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 &frames = bt.get_frames(); + for(list::const_iterator i=frames.begin(); i!=frames.end(); ++i) + out<<*i<<'\n'; + + return out; +} + +ostream &operator<<(ostream &out, const Backtrace::StackFrame &sf) +{ + out< +#include +#include + +namespace Msp { +namespace Debug { + +class Backtrace +{ +public: + struct StackFrame + { + void *address; + std::string file; + std::string symbol; + }; + +private: + std::list frames; + +public: + const std::list &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 index 0000000..053fd52 --- /dev/null +++ b/source/debug/demangle.cpp @@ -0,0 +1,40 @@ +/* $Id$ + +This file is part of libmspcore +Copyright © 2007 Mikko Rasa, Mikkosoft Productions +Distributed under the LGPL +*/ + +#include +#ifdef __GNUC__ +#include +#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 index 0000000..f7c8f3b --- /dev/null +++ b/source/debug/demangle.h @@ -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 + +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 index 0000000..ba69a61 --- /dev/null +++ b/source/debug/profiler.cpp @@ -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::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::iterator i = scopes.insert(map::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::iterator i = scopes.find(scope_name); + if(i==scopes.end()) + { + i = scopes.insert(map::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::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 index 0000000..f2d5812 --- /dev/null +++ b/source/debug/profiler.h @@ -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 +#include +#include +#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 history; + unsigned hist_pos; + std::map called_from; + + ScopeInfo(); + }; + +private: + unsigned period; + std::map 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 index 0000000..3922234 --- /dev/null +++ b/source/debug/profilingscope.cpp @@ -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 index 0000000..132c418 --- /dev/null +++ b/source/debug/profilingscope.h @@ -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 index 0000000..6617f86 --- /dev/null +++ b/source/time/datetime.cpp @@ -0,0 +1,357 @@ +/* $Id$ + +This file is part of libmspcore +Copyright © 2006 Mikko Rasa, Mikkosoft Productions +Distributed under the LGPL +*/ + +#include +#include +#include +#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 +inline int cmp_(T a, T b) +{ + if(ab) + 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=12) ? "PM" : "AM"); + else if(*i=='S') + ss<(offs/Time::min)); + ss<<(offs(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 index 0000000..7c2a349 --- /dev/null +++ b/source/time/datetime.h @@ -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 +#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 index 0000000..c74ffce --- /dev/null +++ b/source/time/timedelta.cpp @@ -0,0 +1,87 @@ +/* $Id$ + +This file is part of libmspcore +Copyright © 2006 Mikko Rasa, Mikkosoft Productions +Distributed under the LGPL +*/ + +#include +#include +#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 +#include +#include +#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 + TimeDelta operator*(T a) const { return TimeDelta(RawTime(usec*a)); } + template + TimeDelta &operator*=(T a) { usec=RawTime(usec*a); return *this; } + + template + TimeDelta operator/(T a) const { return TimeDelta(RawTime(usec/a)); } + template + 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 +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 index 0000000..ab53059 --- /dev/null +++ b/source/time/timer.cpp @@ -0,0 +1,143 @@ +/* $Id$ + +This file is part of libmspcore +Copyright © 2006, 2009 Mikko Rasa, Mikkosoft Productions +Distributed under the LGPL +*/ + +#include +#include "timer.h" +#include "utils.h" + +using namespace std; + +namespace Msp { +namespace Time { + +Timer::~Timer() +{ + for(vector::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::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 index 0000000..26ebf64 --- /dev/null +++ b/source/time/timer.h @@ -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 +#include +#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 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 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 index 0000000..64309ce --- /dev/null +++ b/source/time/timestamp.h @@ -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 usec0 ? 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 index 0000000..9c14624 --- /dev/null +++ b/source/time/timezone.cpp @@ -0,0 +1,154 @@ +/* $Id$ + +This file is part of libmspcore +Copyright © 2008-2009 Mikko Rasa, Mikkosoft Productions +Distributed under the LGPL +*/ + +#include +#include +#include +#ifdef WIN32 +#include +#else +#include +#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(*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; i0) + index = ptr[index]; + ptr += timecnt; + + int abbrind = 0; + for(int i=0; i=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 ? '-' : '+')< +#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 index 0000000..9ea2840 --- /dev/null +++ b/source/time/units.cpp @@ -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 index 0000000..a0ed2b5 --- /dev/null +++ b/source/time/units.h @@ -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 index 0000000..1d0b8d0 --- /dev/null +++ b/source/time/utils.cpp @@ -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 +#else +#include +#include +#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(ft.dwHighDateTime)<<32))/10; + } + + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + return TimeStamp((ft.dwLowDateTime+(static_cast(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 index 0000000..9755356 --- /dev/null +++ b/source/time/utils.h @@ -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 + +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