diff options
Diffstat (limited to 'src/common')
34 files changed, 2323 insertions, 1844 deletions
diff --git a/src/common/const_array.hpp b/src/common/const_array.hpp new file mode 100644 index 0000000..93ae337 --- /dev/null +++ b/src/common/const_array.hpp @@ -0,0 +1,175 @@ +#ifndef CONST_ARRAY_HPP +#define CONST_ARRAY_HPP +// const_array.hpp - just a pointer-to-const and a length +// +// Copyright © 2011-2012 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "sanity.hpp" + +#include <cstring> + +#include <iterator> +#include <ostream> +#include <string> +#include <vector> + +#ifdef WORKAROUND_GCC46_COMPILER +// constexpr is buggy with templates in this version +# define constexpr /* nothing */ +#endif + +// TODO see if I ever actually use this, and not the subclass +template<class T> +class const_array +{ + const T *d; + size_t n; +public: + typedef const T *iterator; + typedef std::reverse_iterator<iterator> reverse_iterator; + + constexpr + const_array(std::nullptr_t) + : d(nullptr), n(0) + {} + + constexpr + const_array(const T *p, size_t z) + : d(p), n(z) + {} + + constexpr + const_array(const T *b, const T *e) + : d(b), n(e - b) + {} + + const_array(std::initializer_list<T> list) + : d(list.begin()), n(list.size()) + {} + + // Implicit conversion from std::vector + const_array(const std::vector<T>& v) + : d(v.data()), n(v.size()) + {} + + // but disallow conversion from a temporary + const_array(std::vector<T>&&) = delete; + + // All methods are non-const to "encourage" you + // to always pass a const_array by value. + // After all, "const const_array" looks funny. + constexpr + const T *data() { return d; } + constexpr + size_t size() { return n; } + constexpr + bool empty() { return not n; } + constexpr explicit + operator bool() { return n; } + + constexpr + std::pair<const_array, const_array> cut(size_t o) + { + return {const_array(d, o), const_array(d + o, n - o)}; + } + + constexpr + const_array first(size_t o) + { + return cut(o).first; + } + + constexpr + const_array last(size_t l) + { + return cut(size() - l).second; + } + + constexpr + const_array after(size_t o) + { + return cut(o).second; + } + + constexpr + iterator begin() { return d; } + constexpr + iterator end() { return d + n; } + constexpr + reverse_iterator rbegin() { return reverse_iterator(end()); } + constexpr + reverse_iterator rend() { return reverse_iterator(begin()); } + + constexpr + const T& front() { return *begin(); } + constexpr + const T& back() { return *rbegin(); } +}; + +// subclass just provides a simpler name and some conversions +class const_string : public const_array<char> +{ +public: + // Implicit conversion from C string. + constexpr + const_string(const char *z) + : const_array<char>(z, z ? strlen(z) : 0) + {} + + // Same as parent constructor. + constexpr + const_string(const char *s, size_t l) + : const_array<char>(s, l) + {} + + // Same as parent constructor. + constexpr + const_string(const char *b, const char *e) + : const_array<char>(b, e) + {} + + // Same as parent constructor. + const_string(const std::vector<char> s) + : const_array<char>(s) + {} + + // Implicit conversion from C++ string. + const_string(const std::string& s) + : const_array<char>(s.data(), s.size()) + {} + + // but disallow converion from a temporary. + const_string(std::string&&) = delete; + + // allow being sloppy + constexpr + const_string(const_array<char> a) + : const_array<char>(a) + {} +}; +#ifdef WORKAROUND_GCC46_COMPILER +# undef constexpr +#endif + +inline +std::ostream& operator << (std::ostream& o, const_string s) +{ + return o.write(s.data(), s.size()); +} + +#endif // CONST_ARRAY_HPP diff --git a/src/common/core.cpp b/src/common/core.cpp index db26e31..994de93 100644 --- a/src/common/core.cpp +++ b/src/common/core.cpp @@ -1,15 +1,18 @@ -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <signal.h> +#include "core.hpp" + #include <sys/wait.h> -#include "core.hpp" +#include <unistd.h> + +#include <csignal> +#include <cstdlib> +#include <ctime> + +#include "random.hpp" #include "socket.hpp" #include "timer.hpp" -#include "version.hpp" -#include "mt_rand.hpp" -#include "nullpo.hpp" + +#include "../poison.hpp" // Added by Gabuzomeu // @@ -17,13 +20,14 @@ // (sigaction() is POSIX; signal() is not.) Taken from Stevens' _Advanced // Programming in the UNIX Environment_. // -typedef void (*sigfunc)(int); -static sigfunc compat_signal (int signo, sigfunc func) +typedef void(*sigfunc)(int); +static +sigfunc compat_signal(int signo, sigfunc func) { struct sigaction sact, oact; sact.sa_handler = func; - sigfillset (&sact.sa_mask); + sigfillset(&sact.sa_mask); sigdelset(&sact.sa_mask, SIGSEGV); sigdelset(&sact.sa_mask, SIGBUS); sigdelset(&sact.sa_mask, SIGTRAP); @@ -31,22 +35,24 @@ static sigfunc compat_signal (int signo, sigfunc func) sigdelset(&sact.sa_mask, SIGFPE); sact.sa_flags = 0; - if (sigaction (signo, &sact, &oact) < 0) + if (sigaction(signo, &sact, &oact) < 0) return SIG_ERR; return oact.sa_handler; } -static void chld_proc (int UNUSED) +static +void chld_proc(int) { wait(NULL); } -static void sig_proc (int UNUSED) +static __attribute__((noreturn)) +void sig_proc(int) { for (int i = 1; i < 31; ++i) compat_signal(i, SIG_IGN); - term_func (); - _exit (0); + term_func(); + _exit(0); } bool runflag = true; @@ -60,34 +66,35 @@ bool runflag = true; Unless you use SA_SIGINFO and *carefully* check the origin, that means they must be SIG_DFL. */ -int main (int argc, char **argv) +int main(int argc, char **argv) { - /// Note that getpid() and getppid() may be very close - mt_seed (time (NULL) ^ (getpid () << 16) ^ (getppid () << 8)); - - do_socket (); + do_socket(); - do_init (argc, argv); + do_init(argc, argv); // set up exit handlers *after* the initialization has happened. // This is because term_func is likely to depend on successful init. - compat_signal (SIGPIPE, SIG_IGN); - compat_signal (SIGTERM, sig_proc); - compat_signal (SIGINT, sig_proc); - compat_signal (SIGCHLD, chld_proc); + compat_signal(SIGPIPE, SIG_IGN); + compat_signal(SIGTERM, sig_proc); + compat_signal(SIGINT, sig_proc); + compat_signal(SIGCHLD, chld_proc); // Signal to create coredumps by system when necessary (crash) - compat_signal (SIGSEGV, SIG_DFL); - compat_signal (SIGBUS, SIG_DFL); - compat_signal (SIGTRAP, SIG_DFL); - compat_signal (SIGILL, SIG_DFL); - compat_signal (SIGFPE, SIG_DFL); + compat_signal(SIGSEGV, SIG_DFL); + compat_signal(SIGBUS, SIG_DFL); + compat_signal(SIGTRAP, SIG_DFL); + compat_signal(SIGILL, SIG_DFL); + compat_signal(SIGFPE, SIG_DFL); - atexit (term_func); + atexit(term_func); while (runflag) { - do_sendrecv (do_timer (gettick_nocache ())); - do_parsepacket (); + // TODO - if timers take a long time to run, this + // may wait too long in sendrecv + tick_t now = milli_clock::now(); + interval_t next = do_timer(now); + do_sendrecv(next); + do_parsepacket(); } } diff --git a/src/common/core.hpp b/src/common/core.hpp index 8a52c55..0c11efb 100644 --- a/src/common/core.hpp +++ b/src/common/core.hpp @@ -1,6 +1,8 @@ #ifndef CORE_HPP #define CORE_HPP -#include <stdbool.h> + +#include "sanity.hpp" + /// core.c contains a server-independent main() function /// and then runs a do_sendrecv loop @@ -10,10 +12,10 @@ extern bool runflag; /// This is an external function defined by each server /// This function must register stuff for the parse loop -extern int do_init (int, char **); +extern int do_init(int, char **); /// Cleanup function called whenever a signal kills us /// or when if we manage to exit() gracefully. -extern void term_func (void); +extern void term_func(void); #endif // CORE_HPP diff --git a/src/common/cxxstdio.cpp b/src/common/cxxstdio.cpp new file mode 100644 index 0000000..8f4001f --- /dev/null +++ b/src/common/cxxstdio.cpp @@ -0,0 +1,33 @@ +#include "cxxstdio.hpp" +// cxxstdio.cpp - pass C++ types through scanf/printf +// +// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include <cstdlib> + +namespace cxxstdio +{ +StringConverter::~StringConverter() +{ + if (mid) + { + out = mid; + free(mid); + } +} +} // namespace cxxstdio diff --git a/src/common/cxxstdio.hpp b/src/common/cxxstdio.hpp new file mode 100644 index 0000000..96c3ca2 --- /dev/null +++ b/src/common/cxxstdio.hpp @@ -0,0 +1,297 @@ +#ifndef CXXSTDIO_HPP +#define CXXSTDIO_HPP +// cxxstdio.hpp - pass C++ types through scanf/printf +// +// Copyright © 2011-2013 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "sanity.hpp" + +#include <cstdarg> +#include <cstdio> + +#include <string> + +#include "const_array.hpp" +#include "utils2.hpp" + + +namespace cxxstdio +{ + inline __attribute__((format(printf, 2, 0))) + int do_vprint(FILE *out, const char *fmt, va_list ap) + { + return vfprintf(out, fmt, ap); + } + + inline __attribute__((format(printf, 2, 0))) + int do_vprint(std::string& out, const char *fmt, va_list ap) + { + int len; + { + va_list ap2; + va_copy(ap2, ap); + len = vsnprintf(nullptr, 0, fmt, ap2); + va_end(ap2); + } + char buffer[len + 1]; + vsnprintf(buffer, len + 1, fmt, ap); + + out = buffer; + return len; + } + + inline __attribute__((format(scanf, 2, 0))) + int do_vscan(FILE *in, const char *fmt, va_list ap) + { + return vfscanf(in, fmt, ap); + } + +#if 0 + inline __attribute__((format(scanf, 2, 0))) + int do_vscan(const char *in, const char *fmt, va_list ap) + { + return vsscanf(in, fmt, ap); + } +#else + inline + int do_vscan(const char *, const char *, va_list) = delete; +#endif + + inline __attribute__((format(scanf, 2, 0))) + int do_vscan(const std::string& in, const char *fmt, va_list ap) + { + return vsscanf(in.c_str(), fmt, ap); + } + + + template<class T> + inline __attribute__((format(printf, 2, 3))) + int do_print(T&& t, const char *fmt, ...) throw() + { + int rv; + va_list ap; + va_start(ap, fmt); + rv = do_vprint(std::forward<T>(t), fmt, ap); + va_end(ap); + return rv; + } + + template<class T> + inline __attribute__((format(scanf, 2, 3))) + int do_scan(T&& t, const char *fmt, ...) throw() + { + int rv; + va_list ap; + va_start(ap, fmt); + rv = do_vscan(std::forward<T>(t), fmt, ap); + va_end(ap); + return rv; + } + + + template<class T> + typename remove_enum<T>::type convert_for_printf(T v) + { + typedef typename remove_enum<T>::type repr_type; + return repr_type(v); + } + + template<class T, typename = typename std::enable_if<!std::is_enum<T>::value>::type> + T& convert_for_scanf(T& v) + { + return v; + } + +#if 0 + template<class E> + constexpr + E get_enum_min_value(decltype(E::min_value)) + { + return E::min_value; + } + template<class E> + constexpr + E get_enum_min_value(E def) + { + return def; + } + + template<class E> + constexpr + E get_enum_max_value(decltype(E::max_value)) + { + return E::max_value; + } + template<class E> + constexpr + E get_enum_max_value(E def) + { + return def; + } +#else + template<class E> + constexpr + E get_enum_min_value(E) + { + return E::min_value; + } + template<class E> + constexpr + E get_max_value(E) + { + return E::max_value; + } +#endif + + template<class E> + class EnumConverter + { + E& out; + typedef typename underlying_type<E>::type U; +#if 0 + constexpr static + U min_value = U(get_enum_min_value<E>(E(std::numeric_limits<U>::min()))); + constexpr static + U max_value = U(get_enum_max_value<E>(E(std::numeric_limits<U>::max()))); +#else + constexpr static + U min_value = U(get_enum_min_value(E())); + constexpr static + U max_value = U(get_enum_max_value(E())); +#endif + U mid; + public: + EnumConverter(E& e) + : out(e), mid(0) + {} + ~EnumConverter() + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (min_value <= mid && mid <= max_value) +#pragma GCC diagnostic pop + out = E(mid); + } + U *operator &() + { + return ∣ + } + }; + + template<class T, typename = typename std::enable_if<std::is_enum<T>::value>::type> + EnumConverter<T> convert_for_scanf(T& v) + { + return v; + } + + + inline + const char *convert_for_printf(const std::string& s) + { + return s.c_str(); + } + + class StringConverter + { + std::string& out; + char *mid; + public: + StringConverter(std::string& s) + : out(s), mid(nullptr) + {} + ~StringConverter(); + char **operator &() + { + return ∣ + } + }; + + inline + StringConverter convert_for_scanf(std::string& s) + { + return StringConverter(s); + } + + template<class Format> + class PrintFormatter + { + public: + template<class T, class... A> + static + int print(T&& t, A&&... a) + { + constexpr static + const char *print_format = Format::print_format(); + return do_print(std::forward<T>(t), print_format, + convert_for_printf(std::forward<A>(a))...); + } + }; + + template<class Format> + class ScanFormatter + { + public: + template<class T, class... A> + static + int scan(T&& t, A&&... a) + { + constexpr static + const char *scan_format = Format::scan_format(); + return do_scan(std::forward<T>(t), scan_format, + &convert_for_scanf(*a)...); + } + }; + +#define FPRINTF(file, fmt, ...) \ + ([&]() -> int \ + { \ + struct format_impl \ + { \ + constexpr static \ + const char *print_format() { return fmt; } \ + }; \ + return cxxstdio::PrintFormatter<format_impl>::print(file, ## __VA_ARGS__); \ + }()) + +#define FSCANF(file, fmt, ...) \ + ([&]() -> int \ + { \ + struct format_impl \ + { \ + constexpr static \ + const char *scan_format() { return fmt; } \ + }; \ + return cxxstdio::ScanFormatter<format_impl>::scan(file, ## __VA_ARGS__); \ + }()) + +#define PRINTF(fmt, ...) FPRINTF(stdout, fmt, ## __VA_ARGS__) +#define SPRINTF(str, fmt, ...) FPRINTF(str, fmt, ## __VA_ARGS__) +#define SCANF(fmt, ...) FSCANF(stdin, fmt, ## __VA_ARGS__) +#define SSCANF(str, fmt, ...) FSCANF(str, fmt, ## __VA_ARGS__) + +#define STRPRINTF(fmt, ...) \ + ([&]() -> std::string \ + { \ + std::string _out_impl; \ + SPRINTF(_out_impl, fmt, ## __VA_ARGS__);\ + return _out_impl; \ + }()) + +} // namespace cxxstdio + +#endif // CXXSTDIO_HPP diff --git a/src/common/db.cpp b/src/common/db.cpp index 21a3597..cc58ad8 100644 --- a/src/common/db.cpp +++ b/src/common/db.cpp @@ -1,546 +1,3 @@ #include "db.hpp" -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "utils.hpp" - -#define ROOT_SIZE 4096 - -static int strdb_cmp (struct dbt *table, const char *a, const char* b) -{ - if (table->maxlen) - return strncmp (a, b, table->maxlen); - return strcmp (a, b); -} - -static hash_t strdb_hash (struct dbt *table, const char *a) -{ - size_t i = table->maxlen; - if (i == 0) - i = (size_t)-1; - hash_t h = 0; - const unsigned char *p = (const unsigned char*)a; - while (*p && i--) - { - h = (h * 33 + *p++) ^ (h >> 24); - } - return h; -} - -struct dbt *strdb_init (size_t maxlen) -{ - struct dbt *table; - CREATE (table, struct dbt, 1); - table->type = DB_STRING; - table->maxlen = maxlen; - return table; -} - -static int numdb_cmp (numdb_key_t a, numdb_key_t b) -{ - if (a == b) - return 0; - if (a < b) - return -1; - return 1; -} - -static hash_t numdb_hash (numdb_key_t a) -{ - return (hash_t) a; -} - -struct dbt *numdb_init (void) -{ - struct dbt *table; - CREATE (table, struct dbt, 1); - table->type = DB_NUMBER; - return table; -} - -static int table_cmp (struct dbt *table, db_key_t a, db_key_t b) -{ - switch(table->type) - { - case DB_NUMBER: return numdb_cmp (a.i, b.i); - case DB_STRING: return strdb_cmp (table, a.s, b.s); - } - abort(); -} - -static hash_t table_hash (struct dbt *table, db_key_t key) -{ - switch(table->type) - { - case DB_NUMBER: return numdb_hash (key.i); - case DB_STRING: return strdb_hash (table, key.s); - } - abort(); -} - -/// Search for a node with the given key -db_val_t db_search (struct dbt *table, db_key_t key) -{ - struct dbn *p = table->ht[table_hash (table, key) % HASH_SIZE]; - - while (p) - { - int c = table_cmp (table, key, p->key); - if (c == 0) - return p->data; - if (c < 0) - p = p->left; - else - p = p->right; - } - return NULL; -} - -// Tree maintainance methods -static void db_rotate_left (struct dbn *p, struct dbn **root) -{ - struct dbn *y = p->right; - p->right = y->left; - if (y->left) - y->left->parent = p; - y->parent = p->parent; - - if (p == *root) - *root = y; - else if (p == p->parent->left) - p->parent->left = y; - else - p->parent->right = y; - y->left = p; - p->parent = y; -} - -static void db_rotate_right (struct dbn *p, struct dbn **root) -{ - struct dbn *y = p->left; - p->left = y->right; - if (y->right) - y->right->parent = p; - y->parent = p->parent; - - if (p == *root) - *root = y; - else if (p == p->parent->right) - p->parent->right = y; - else - p->parent->left = y; - y->right = p; - p->parent = y; -} - -static void db_rebalance (struct dbn *p, struct dbn **root) -{ - p->color = RED; - while (p != *root && p->parent->color == RED) - { - if (p->parent == p->parent->parent->left) - { - struct dbn *y = p->parent->parent->right; - if (y && y->color == RED) - { - p->parent->color = BLACK; - y->color = BLACK; - p->parent->parent->color = RED; - p = p->parent->parent; - } - else - { - if (p == p->parent->right) - { - p = p->parent; - db_rotate_left (p, root); - } - p->parent->color = BLACK; - p->parent->parent->color = RED; - db_rotate_right (p->parent->parent, root); - } - } - else - { - struct dbn *y = p->parent->parent->left; - if (y && y->color == RED) - { - p->parent->color = BLACK; - y->color = BLACK; - p->parent->parent->color = RED; - p = p->parent->parent; - } - else - { - if (p == p->parent->left) - { - p = p->parent; - db_rotate_right (p, root); - } - p->parent->color = BLACK; - p->parent->parent->color = RED; - db_rotate_left (p->parent->parent, root); - } - } - } - (*root)->color = BLACK; -} - -// param z = node to remove -static void db_rebalance_erase (struct dbn *z, struct dbn **root) -{ - struct dbn *y = z; - struct dbn *x = NULL; - - if (!y->left) - x = y->right; - else if (!y->right) - x = y->left; - else - { - y = y->right; - while (y->left) - y = y->left; - x = y->right; - } - struct dbn *x_parent = NULL; - if (y != z) - { - z->left->parent = y; - y->left = z->left; - if (y != z->right) - { - x_parent = y->parent; - if (x) - x->parent = y->parent; - y->parent->left = x; - y->right = z->right; - z->right->parent = y; - } - else - x_parent = y; - if (*root == z) - *root = y; - else if (z->parent->left == z) - z->parent->left = y; - else - z->parent->right = y; - y->parent = z->parent; - { - dbn_color tmp = y->color; - y->color = z->color; - z->color = tmp; - } - y = z; - } - else - { - x_parent = y->parent; - if (x) - x->parent = y->parent; - if (*root == z) - *root = x; - else if (z->parent->left == z) - z->parent->left = x; - else - z->parent->right = x; - } - if (y->color != RED) - { - while (x != *root && (!x || x->color == BLACK)) - if (x == x_parent->left) - { - struct dbn *w = x_parent->right; - if (w->color == RED) - { - w->color = BLACK; - x_parent->color = RED; - db_rotate_left (x_parent, root); - w = x_parent->right; - } - if ((!w->left || w->left->color == BLACK) && - (!w->right || w->right->color == BLACK)) - { - w->color = RED; - x = x_parent; - x_parent = x->parent; - } - else - { - if (!w->right|| w->right->color == BLACK) - { - if (w->left) - w->left->color = BLACK; - w->color = RED; - db_rotate_right (w, root); - w = x_parent->right; - } - w->color = x_parent->color; - x_parent->color = BLACK; - if (w->right) - w->right->color = BLACK; - db_rotate_left (x_parent, root); - break; - } - } - else - { - // same as above, with right <-> left. - struct dbn *w = x_parent->left; - if (w->color == RED) - { - w->color = BLACK; - x_parent->color = RED; - db_rotate_right (x_parent, root); - w = x_parent->left; - } - if ((!w->right || w->right->color == BLACK) && - (!w->left || w->left->color == BLACK)) - { - w->color = RED; - x = x_parent; - x_parent = x_parent->parent; - } - else - { - if (!w->left || w->left->color == BLACK) - { - if (w->right) - w->right->color = BLACK; - w->color = RED; - db_rotate_left (w, root); - w = x_parent->left; - } - w->color = x_parent->color; - x_parent->color = BLACK; - if (w->left) - w->left->color = BLACK; - db_rotate_right (x_parent, root); - break; - } - } - if (x) - x->color = BLACK; - } -} - -struct dbn *db_insert (struct dbt *table, db_key_t key, db_val_t data) -{ - hash_t hash = table_hash (table, key) % HASH_SIZE; - int c = 0; - struct dbn *prev = NULL; - struct dbn *p = table->ht[hash]; - while (p) - { - c = table_cmp (table, key, p->key); - if (c == 0) - { - // key found in table, replace - // Tell the user of the table to free the key and value - if (table->release) - table->release (p->key, p->data); - p->data = data; - p->key = key; - return p; - } - // prev is always p->parent? - prev = p; - if (c < 0) - p = p->left; - else - p = p->right; - } - CREATE (p, struct dbn, 1); - p->key = key; - p->data = data; - p->color = RED; - if (c == 0) - { // hash entry is empty - table->ht[hash] = p; - p->color = BLACK; - return p; - } - p->parent = prev; - if (c < 0) - prev->left = p; - else - prev->right = p; - if (prev->color == RED) - { - // must rebalance - db_rebalance (p, &table->ht[hash]); - } - return p; -} - -db_val_t db_erase (struct dbt *table, db_key_t key) -{ - hash_t hash = table_hash (table, key) % HASH_SIZE; - struct dbn *p = table->ht[hash]; - while (p) - { - int c = table_cmp (table, key, p->key); - if (c == 0) - break; - if (c < 0) - p = p->left; - else - p = p->right; - } - if (!p) - return NULL; - db_val_t data = p->data; - db_rebalance_erase (p, &table->ht[hash]); - free (p); - return data; -} -#ifdef SMART_WALK_TREE -static inline void db_walk_tree (bool dealloc, struct dbn* p, db_func_t func, va_list ap) -{ - if (!p) - return; - if (!dealloc && !func) - { - fprintf(stderr, "DEBUG: Must walk tree to either free or invoke a function.\n"); - abort(); - } - if (p->parent) - { - fprintf(stderr, "DEBUG: Root nodes must not have parents\n"); - abort(); - } - while (true) - { - // apply_func loop - if (func) - func (p->key, p->data, ap); - if (p->left) - { - // continue descending - p = p->left; - continue; //goto apply_func; - } - if (p->right) - { - // descending the other side - p = p->right; - continue; //goto apply_func; - } - while (true) - { - // backtrack loop - if (!p->parent) - { - if (dealloc) - free (p); - // if we have already done both children, there is no more to do - return; - } - if (p->parent->left == p && p->parent->right) - { - // finished the left tree, now walk the right tree - p = p->parent->right; - if (dealloc) - free (p->parent->left); - break; //goto apply_func; - } - // p->parent->right == p - // or p->parent->left == p but p->parent->right == NULL - // keep backtracking - p = p->parent; - if (dealloc) - free (p->right?:p->left); - } //backtrack loop - } // apply_func loop -} -#endif // SMART_WALK_TREE - -void db_foreach (struct dbt *table, db_func_t func, ...) -{ - va_list ap; - va_start (ap, func); - - for (int i = 0; i < HASH_SIZE; i++) - { -#ifdef SMART_WALK_TREE - db_walk_tree (false, table->ht[i], func, ap); -#else - struct dbn *p = table->ht[i]; - if (!p) - continue; - struct dbn *stack[64]; - int sp = 0; - while (1) - { - func (p->key, p->data, ap); - struct dbn *pn = p->left; - if (pn) - { - if (p->right) - stack[sp++] = p->right; - p = pn; - } - else // pn == NULL, time to do the right branch - { - if (p->right) - p = p->right; - else - { - if (sp == 0) - break; - p = stack[--sp]; - } - } // if pn else if !pn - } // while true -#endif // else ! SMART_WALK_TREE - } // for i - va_end (ap); -} - -// This function is suspiciously similar to the previous -void db_final (struct dbt *table, db_func_t func, ...) -{ - va_list ap; - va_start (ap, func); - - for (int i = 0; i < HASH_SIZE; i++) - { -#ifdef SMART_WALK_TREE - db_walk_tree (true, table->ht[i], func, ap); -#else - struct dbn *p = table->ht[i]; - if (!p) - continue; - struct dbn *stack[64]; - int sp = 0; - while (1) - { - if (func) - func (p->key, p->data, ap); - struct dbn *pn = p->left; - if (pn) - { - if (p->right) - stack[sp++] = p->right; - } - else // pn == NULL, check the right - { - if (p->right) - pn = p->right; - else - { - if (sp == 0) - break; - pn = stack[--sp]; - } - } // if pn else if !pn - free (p); - p = pn; - } // while true -#endif // else ! SMART_WALK_TREE - } // for i - free (table); - va_end (ap); -} +#include "../poison.hpp" diff --git a/src/common/db.hpp b/src/common/db.hpp index 62125d8..bd47928 100644 --- a/src/common/db.hpp +++ b/src/common/db.hpp @@ -1,90 +1,123 @@ -// WARNING: there is a system header by this name #ifndef DB_HPP #define DB_HPP -# include "sanity.hpp" +// db.hpp - convenience wrappers over std::map<K, V> +// +// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. -# include <stdarg.h> +# include "sanity.hpp" -/// Number of tree roots -// Somewhat arbitrary - larger wastes more space but is faster for large trees -// num % HASH_SIZE minimize collisions even for similar num -# define HASH_SIZE (256+27) +# include <map> -typedef enum dbn_color +template<class K, class V> +class Map { - RED, - BLACK -} dbn_color; + typedef std::map<K, V> Impl; -typedef intptr_t numdb_key_t; -typedef union db_key_t -{ - char *ms __attribute__((deprecated)); - const char* s; - numdb_key_t i; + Impl impl; +public: + Map() = default; + Map(std::initializer_list<std::pair<const K, V>> il) + : impl(il) + {} + typedef typename Impl::iterator iterator; + typedef typename Impl::const_iterator const_iterator; - db_key_t(numdb_key_t n) : i(n) {} - db_key_t(const char * z) : s(z) {} -} db_key_t; -typedef void* db_val_t; -typedef uint32_t hash_t; -typedef void (*db_func_t)(db_key_t, db_val_t, va_list); + iterator begin() { return impl.begin(); } + iterator end() { return impl.end(); } + const_iterator begin() const { return impl.begin(); } + const_iterator end() const { return impl.end(); } -/// DataBase Node -struct dbn -{ - struct dbn *parent, *left, *right; - dbn_color color; - db_key_t key; - db_val_t data; -}; + V *search(const K& k) + { + iterator it = impl.find(k); + if (it == impl.end()) + return nullptr; + return &it->second; + } + const V *search(const K& k) const + { + const_iterator it = impl.find(k); + if (it == impl.end()) + return nullptr; + return &it->second; + } + void insert(K k, V v) + { + // As far as I can tell, this is the simplest way to + // implement move-only insert-with-replacement. + iterator it = impl.lower_bound(k); + // invariant: if it is valid, it->first >= k + if (it != impl.end() && it->first == k) + it->second = std::move(v); + else + it = impl.insert(std::pair<K, V>(std::move(k), std::move(v))).first; + return (void)&it->second; -typedef enum dbt_type -{ - DB_NUMBER, - DB_STRING, -} dbt_type; + } + V *init(K k) + { + return &impl[k]; + } + void erase(const K& k) + { + impl.erase(k); + } + void clear() + { + impl.clear(); + } + bool empty() + { + return impl.empty(); + } +}; -/// DataBase Table -struct dbt +template<class K, class V> +class DMap { - dbt_type type; - /// Note, before replacement, key/values to be replaced - // TODO refactor to decrease/eliminate the uses of this? - void (*release) (db_key_t, db_val_t) __attribute__((deprecated)); - /// Maximum length of a string key - TODO refactor to ensure all strings are NUL-terminated - size_t maxlen __attribute__((deprecated)); - /// The root trees - struct dbn *ht[HASH_SIZE]; -}; + typedef Map<K, V> Impl; -# define strdb_search(t,k) db_search((t),(db_key_t)(k)) -# define strdb_insert(t,k,d) db_insert((t),(db_key_t)(k),(db_val_t)(d)) -# define strdb_erase(t,k) db_erase ((t),(db_key_t)(k)) -# define strdb_foreach db_foreach -# define strdb_final db_final -# define numdb_search(t,k) db_search((t),(db_key_t)(k)) -# define numdb_insert(t,k,d) db_insert((t),(db_key_t)(k),(db_val_t)(d)) -# define numdb_erase(t,k) db_erase ((t),(db_key_t)(k)) -# define numdb_foreach db_foreach -# define numdb_final db_final + Impl impl; +public: + typedef typename Impl::iterator iterator; + typedef typename Impl::const_iterator const_iterator; -/// Create a map from char* to void*, with strings possibly not null-terminated -struct dbt *strdb_init (size_t maxlen); -/// Create a map from int to void* -struct dbt *numdb_init (void); -/// Return the value corresponding to the key, or NULL if not found -db_val_t db_search (struct dbt *table, db_key_t key); -/// Add or replace table[key] = data -// if it was already there, call release -struct dbn *db_insert (struct dbt *table, db_key_t key, db_val_t data); -/// Remove a key from the table, returning the data -db_val_t db_erase (struct dbt *table, db_key_t key); + iterator begin() { return impl.begin(); } + iterator end() { return impl.end(); } + const_iterator begin() const { return impl.begin(); } + const_iterator end() const { return impl.end(); } -/// Execute a function for every element, in unspecified order -void db_foreach (struct dbt *, db_func_t, ...); -// opposite of init? Calls release for every element and frees memory -// This probably isn't really needed: we don't have to free memory while exiting -void db_final (struct dbt *, db_func_t, ...) __attribute__((deprecated)); + V get(K k) + { + V *vp = impl.search(k); + return vp ? *vp : V(); + } + void put(K k, V v) + { + if (v == V()) + impl.erase(k); + else + impl.insert(k, v); + } + void clear() + { + impl.clear(); + } +}; -#endif +#endif // DB_HPP diff --git a/src/common/extract.cpp b/src/common/extract.cpp new file mode 100644 index 0000000..5e89e19 --- /dev/null +++ b/src/common/extract.cpp @@ -0,0 +1,68 @@ +#include "extract.hpp" +// extract.cpp - a simple, hierarchical, tokenizer +// +// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +bool extract(const_string str, const_string *rv) +{ + *rv = str; + return true; +} + +bool extract(const_string str, std::string *rv) +{ + *rv = std::string(str.begin(), str.end()); + return true; +} + +bool extract(const_string str, struct global_reg *var) +{ + return extract(str, + record<','>(&var->str, &var->value)); +} + +bool extract(const_string str, struct item *it) +{ + return extract(str, + record<','>( + &it->id, + &it->nameid, + &it->amount, + &it->equip, + &it->identify, + &it->refine, + &it->attribute, + &it->card[0], + &it->card[1], + &it->card[2], + &it->card[3], + &it->broken)) + || extract(str, + record<','>( + &it->id, + &it->nameid, + &it->amount, + &it->equip, + &it->identify, + &it->refine, + &it->attribute, + &it->card[0], + &it->card[1], + &it->card[2], + &it->card[3])); +} diff --git a/src/common/extract.hpp b/src/common/extract.hpp new file mode 100644 index 0000000..ae1a74b --- /dev/null +++ b/src/common/extract.hpp @@ -0,0 +1,166 @@ +#ifndef EXTRACT_HPP +#define EXTRACT_HPP +// extract.hpp - a simple, hierarchical, tokenizer +// +// Copyright © 2012-2013 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "sanity.hpp" + +#include <algorithm> + +#include "const_array.hpp" +#include "mmo.hpp" +#include "utils.hpp" + +template<class T, typename=typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, char>::value>::type> +bool extract(const_string str, T *iv) +{ + if (!str || str.size() > 20) + return false; + if (!((str.front() == '-' && std::is_signed<T>::value) + || ('0' <= str.front() && str.front() <= '9'))) + return false; + char buf[20 + 1]; + std::copy(str.begin(), str.end(), buf); + buf[str.size()] = '\0'; + + char *end; + errno = 0; + if (std::is_signed<T>::value) + { + long long v = strtoll(buf, &end, 10); + if (errno || *end) + return false; + *iv = v; + return *iv == v; + } + else + { + unsigned long long v = strtoull(buf, &end, 10); + if (errno || *end) + return false; + *iv = v; + return *iv == v; + } +} + +inline +bool extract(const_string str, TimeT *tv) +{ + return extract(str, &tv->value); +} + +// extra typename=void to workaround some duplicate overload rule +template<class T, typename=typename std::enable_if<std::is_enum<T>::value>::type, typename=void> +bool extract(const_string str, T *iv) +{ + typedef typename underlying_type<T>::type U; + U v; + // defer to integer version + if (!extract(str, &v)) + return false; + // TODO check bounds using enum min/max as in SSCANF + *iv = static_cast<T>(v); + return true; +} + +bool extract(const_string str, const_string *rv); + +bool extract(const_string str, std::string *rv); + +template<size_t N> +__attribute__((deprecated)) +bool extract(const_string str, char (*out)[N]) +{ + if (str.size() >= N) + return false; + std::copy(str.begin(), str.end(), *out); + std::fill(*out + str.size() , *out + N, '\0'); + return true; +} + +// basically just a std::tuple +// but it provides its data members publically +template<char split, class... T> +class Record; +template<char split> +class Record<split> +{ +}; +template<char split, class F, class... R> +class Record<split, F, R...> +{ +public: + F frist; + Record<split, R...> rest; +public: + Record(F f, R... r) + : frist(f), rest(r...) + {} +}; +template<char split, class... T> +Record<split, T...> record(T... t) +{ + return Record<split, T...>(t...); +} + +template<char split> +bool extract(const_string str, Record<split>) +{ + return !str; +} +template<char split, class F, class... R> +bool extract(const_string str, Record<split, F, R...> rec) +{ + const char *s = std::find(str.begin(), str.end(), split); + if (s == str.end()) + return sizeof...(R) == 0 && extract(str, rec.frist); + return extract(const_string(str.begin(), s), rec.frist) + && extract(const_string(s + 1, str.end()), rec.rest); +} + +template<char split, class T> +struct VRecord +{ + std::vector<T> *arr; +}; + +template<char split, class T> +VRecord<split, T> vrec(std::vector<T> *arr) +{ + return {arr}; +} + +template<char split, class T> +bool extract(const_string str, VRecord<split, T> rec) +{ + if (str.empty()) + return true; + const char *s = std::find(str.begin(), str.end(), split); + rec.arr->emplace_back(); + if (s == str.end()) + return extract(str, &rec.arr->back()); + return extract(const_string(str.begin(), s), &rec.arr->back()) + && extract(const_string(s + 1, str.end()), rec); +} + +bool extract(const_string str, struct global_reg *var); + +bool extract(const_string str, struct item *it); + +#endif // EXTRACT_HPP diff --git a/src/common/grfio.cpp b/src/common/grfio.cpp deleted file mode 100644 index dd1e707..0000000 --- a/src/common/grfio.cpp +++ /dev/null @@ -1,216 +0,0 @@ -// Reads .gat files by name-mapping .wlk files -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> - -#include "utils.hpp" -#include "grfio.hpp" -#include "mmo.hpp" -#include "socket.hpp" - -//---------------------------- -// file entry table struct -//---------------------------- -typedef struct -{ - size_t declen; - int16_t next; // next index into the filelist[] array, or -1 - char fn[128 - 4 - 2]; // file name -} FILELIST; - -#define FILELIST_LIMIT 32768 // limit to number of filelists - if you increase this, change all shorts to int -#define FILELIST_ADDS 1024 // amount to increment when reallocing - -static FILELIST *filelist = NULL; -/// Number of entries used -static uint16_t filelist_entrys = 0; -/// Number of FILELIST entries actually allocated -static uint16_t filelist_maxentry = 0; - -/// First index of the given hash, into the filelist[] array -#define l -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 -static int16_t filelist_hash[256] = {l,l,l,l,l,l,l,l,l,l,l,l,l,l,l,l}; -#undef l - -/// Hash a filename -static uint8_t filehash (const char *fname) -{ - // Larger than the return type - upper bits are used in the process - uint32_t hash = 0; - while (*fname) - { - hash = (hash << 1) + (hash >> 7) * 9 + (unsigned char)*fname; - fname++; - } - return hash; -} - -/// Find the filelist entry for the given filename, or NULL if it is not -static -FILELIST *filelist_find (const char *fname) -{ - int16_t index = filelist_hash[filehash (fname)]; - while (index >= 0) - { - if (strcmp (filelist[index].fn, fname) == 0) - return &filelist[index]; - index = filelist[index].next; - } - return NULL; -} - -/// Copy a temporary entry into the hash map -static FILELIST *filelist_add (FILELIST * entry) -{ - if (filelist_entrys >= FILELIST_LIMIT) - { - fprintf (stderr, "filelist limit : filelist_add\n"); - exit (1); - } - - if (filelist_entrys >= filelist_maxentry) - { - RECREATE(filelist, FILELIST, filelist_maxentry + FILELIST_ADDS); - memset (filelist + filelist_maxentry, '\0', - FILELIST_ADDS * sizeof (FILELIST)); - filelist_maxentry += FILELIST_ADDS; - } - - uint16_t new_index = filelist_entrys++; - uint8_t hash = filehash (entry->fn); - entry->next = filelist_hash[hash]; - filelist_hash[hash] = new_index; - - filelist[new_index] = *entry; - - return &filelist[new_index]; -} - -static FILELIST *filelist_modify (FILELIST * entry) -{ - FILELIST *fentry = filelist_find (entry->fn); - if (fentry) - { - entry->next = fentry->next; - *fentry = *entry; - return fentry; - } - return filelist_add (entry); -} - -/// Change fname data/*.gat to lfname data/*.wlk -// TODO even if the file exists, don't keep reopening it every time one loads -static -void grfio_resnametable (const char *fname, char *lfname) -{ - char restable[] = "data/resnametable.txt"; - - FILE *fp = fopen_ (restable, "rb"); - if (fp == NULL) - { - fprintf(stderr, "No resnametable, can't look for %s\n", fname); - strcpy(lfname, fname); - char* ext = lfname + strlen(lfname) - 4; - if (!strcmp(ext, ".gat")) - strcpy(ext, ".wlk"); - return; - } - - char line[512]; - while (fgets (line, sizeof (line), fp)) - { - char w1[256], w2[256]; - if ( - // line is of the form foo.gat#foo.wlk# - (sscanf (line, "%[^#]#%[^#]#", w1, w2) == 2) - // strip data/ from foo.gat before comparing - && (!strcmp (w1, fname + 5))) - { - strcpy (lfname, "data/"); - strcpy (lfname + 5, w2); - fclose_ (fp); - return; - } - } - fprintf(stderr, "Unable to find resource: %s\n", fname); - fclose_ (fp); - - strcpy(lfname, fname); - char* ext = lfname + strlen(lfname) - 4; - if (!strcmp(ext, ".gat")) - strcpy(ext, ".wlk"); - return; -} - -/// Size of resource -size_t grfio_size (const char *fname) -{ - FILELIST *entry = filelist_find (fname); - if (entry) - return entry->declen; - - char lfname[256]; - FILELIST lentry; - struct stat st; - - grfio_resnametable (fname, lfname); - - for (char *p = lfname; *p; p++) - if (*p == '\\') - *p = '/'; - - if (stat (lfname, &st) == 0) - { - strncpy (lentry.fn, fname, sizeof (lentry.fn) - 1); - lentry.declen = st.st_size; - entry = filelist_modify (&lentry); - } - else - { - printf ("%s not found\n", fname); - return 0; - } - return entry->declen; -} - -void *grfio_reads (const char *fname, size_t *size) -{ - char lfname[256]; - grfio_resnametable (fname, lfname); - - for (char *p = &lfname[0]; *p != 0; p++) - if (*p == '\\') - *p = '/'; // * At the time of Unix - - FILE *in = fopen_ (lfname, "rb"); - if (!in) - { - fprintf (stderr, "%s not found\n", fname); - return NULL; - } - FILELIST lentry; - FILELIST *entry = filelist_find (fname); - if (entry) - { - lentry.declen = entry->declen; - } - else - { - fseek (in, 0, SEEK_END); - lentry.declen = ftell (in); - fseek (in, 0, SEEK_SET); - strncpy (lentry.fn, fname, sizeof (lentry.fn) - 1); - entry = filelist_modify (&lentry); - } - uint8_t *buf2; - CREATE (buf2, uint8_t, lentry.declen + 1024); - if (fread (buf2, 1, lentry.declen, in) != lentry.declen) - exit(1); - fclose_ (in); - in = NULL; - - if (size) - *size = entry->declen; - return buf2; -} diff --git a/src/common/grfio.hpp b/src/common/grfio.hpp deleted file mode 100644 index 3485904..0000000 --- a/src/common/grfio.hpp +++ /dev/null @@ -1,17 +0,0 @@ -/// Accessor to the .gat map virtual files -// Note .gat files are mapped to .wlk files by data/resnametable.txt -// Note that there currently is a 1-1 correlation between them, -// but it is possible for a single .wlk to have multiple .gats reference it -#ifndef GRFIO_HPP -#define GRFIO_HPP - -/// Load file into memory -# define grfio_read(resourcename) grfio_reads (resourcename, NULL) -/// Load file into memory and possibly record length -// For some reason, this allocates an extra 1024 bytes at the end -void *grfio_reads (const char *resourcename, size_t *size); -/// Get size of file -// This is only called once, and that is to check the existence of a file. -size_t grfio_size (const char *resourcename) __attribute__((deprecated)); - -#endif // GRFIO_HPP diff --git a/src/common/lock.cpp b/src/common/lock.cpp index 2ba9a0a..82856e1 100644 --- a/src/common/lock.cpp +++ b/src/common/lock.cpp @@ -1,36 +1,56 @@ -#include <unistd.h> -#include <stdio.h> #include "lock.hpp" + +#include <unistd.h> + +#include <cstdio> + +#include "cxxstdio.hpp" #include "socket.hpp" +#include "../poison.hpp" + +/// number of backups to keep +static +const int backup_count = 10; + /// Protected file writing /// (Until the file is closed, it keeps the old file) // Start writing a tmpfile -FILE *lock_fopen (const char *filename, int *info) +FILE *lock_fopen(const char *filename, int *info) { - char newfile[512]; FILE *fp; - int no = getpid (); + int no = getpid(); // Get a filename that doesn't already exist + std::string newfile; do { - sprintf (newfile, "%s_%d.tmp", filename, no++); + newfile = STRPRINTF("%s_%d.tmp", filename, no++); + fp = fopen_(newfile.c_str(), "wx"); } - while ((fp = fopen_ (newfile, "r")) && (fclose_ (fp), 1)); + while (!fp); *info = --no; - return fopen_ (newfile, "w"); + return fp; } // Delete the old file and rename the new file -void lock_fclose (FILE * fp, const char *filename, int *info) +void lock_fclose(FILE *fp, const char *filename, int *info) { - char newfile[512]; if (fp) { - fclose_ (fp); - sprintf (newfile, "%s_%d.tmp", filename, *info); - rename (newfile, filename); + fclose_(fp); + int n = backup_count; + std::string old_filename = STRPRINTF("%s.%d", filename, n); + while (--n) + { + std::string newer_filename = STRPRINTF("%s.%d", filename, n); + rename(newer_filename.c_str(), old_filename.c_str()); + old_filename = std::move(newer_filename); + } + rename(filename, old_filename.c_str()); + + std::string tmpfile = STRPRINTF("%s_%d.tmp", filename, *info); + rename(tmpfile.c_str(), filename); } } diff --git a/src/common/lock.hpp b/src/common/lock.hpp index 19c1302..f7ce2d8 100644 --- a/src/common/lock.hpp +++ b/src/common/lock.hpp @@ -1,8 +1,15 @@ #ifndef LOCK_HPP #define LOCK_HPP + +#include "sanity.hpp" + +#include <cstdio> + +// TODO replace with a class + /// Locked FILE I/O // Changes are made in a separate file until lock_fclose -FILE *lock_fopen (const char *filename, int *info); -void lock_fclose (FILE * fp, const char *filename, int *info); +FILE *lock_fopen(const char *filename, int *info); +void lock_fclose(FILE * fp, const char *filename, int *info); #endif // LOCK_HPP diff --git a/src/common/md5calc.cpp b/src/common/md5calc.cpp index b0f8e5f..1625912 100644 --- a/src/common/md5calc.cpp +++ b/src/common/md5calc.cpp @@ -1,6 +1,10 @@ #include "md5calc.hpp" -#include <string.h> -#include "mt_rand.hpp" + +#include <cstring> + +#include "random.hpp" + +#include "../poison.hpp" // auxilary data /* @@ -9,7 +13,8 @@ sin() constant table echo 'scale=40; obase=16; for (i=1;i<=64;i++) print 2^32 * sin(i), "\n"' | bc | sed 's/^-//;s/^/0x/;s/\..*$/,/' */ -static const uint32_t T[64] = +static +const uint32_t T[64] = { // used by round 1 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, //0 @@ -35,29 +40,35 @@ static const uint32_t T[64] = // auxilary functions // note - the RFC defines these by non-CS conventions: or=v, and=(empty) -static inline uint32_t rotate_left(uint32_t val, unsigned shift) +static +uint32_t rotate_left(uint32_t val, unsigned shift) { return val << shift | val >> (32-shift); } -static inline uint32_t F(uint32_t X, uint32_t Y, uint32_t Z) +static +uint32_t F(uint32_t X, uint32_t Y, uint32_t Z) { return (X & Y) | (~X & Z); } -static inline uint32_t G(uint32_t X, uint32_t Y, uint32_t Z) +static +uint32_t G(uint32_t X, uint32_t Y, uint32_t Z) { return (X & Z) | (Y & ~Z); } -static inline uint32_t H(uint32_t X, uint32_t Y, uint32_t Z) +static +uint32_t H(uint32_t X, uint32_t Y, uint32_t Z) { return X ^ Y ^ Z; } -static inline uint32_t I(uint32_t X, uint32_t Y, uint32_t Z) +static +uint32_t I(uint32_t X, uint32_t Y, uint32_t Z) { return Y ^ (X | ~Z); } -static const struct +static +const struct { uint8_t k : 4; uint8_t : 0; @@ -165,7 +176,8 @@ void MD5_to_bin(MD5_state state, uint8_t out[0x10]) out[i] = state.val[i/4] >> 8*(i%4); } -static const char hex[] = "0123456789abcdef"; +static +const char hex[] = "0123456789abcdef"; void MD5_to_str(MD5_state state, char out[0x21]) { @@ -193,7 +205,7 @@ MD5_state MD5_from_string(const char* msg, const size_t msglen) } // now pad 1-512 bits + the 64-bit length - may be two blocks uint8_t buf[0x40] = {}; - memcpy (buf, msg, rem); + memcpy(buf, msg, rem); buf[rem] = 0x80; // a single one bit if (64 - rem > 8) { @@ -293,8 +305,9 @@ const char *MD5_saltcrypt(const char *key, const char *salt) const char *make_salt(void) { static char salt[6]; - for (int i=0; i<5; i++) - salt[i] = MPRAND(48, 78); + for (int i = 0; i < 5; i++) + // 126 would probably actually be okay + salt[i] = random_::in(48, 125); return salt; } diff --git a/src/common/md5calc.hpp b/src/common/md5calc.hpp index 2dfaecb..de19e0f 100644 --- a/src/common/md5calc.hpp +++ b/src/common/md5calc.hpp @@ -5,9 +5,9 @@ #include <netinet/in.h> -#include <stdint.h> // uint32_t, uint8_t -#include <stddef.h> // size_t -#include <stdio.h> // FILE* +#include <cstdint> +#include <cstddef> +#include <cstdio> /// The digest state - becomes the output typedef struct @@ -35,18 +35,6 @@ MD5_state MD5_from_cstring(const char* msg); MD5_state MD5_from_FILE(FILE* in); -/// Output in ASCII - with lowercase hex digits, null-terminated -// these may overlap safely -static void MD5_String (const char *string, char output[33]) __attribute__((deprecated)); -static inline void MD5_String (const char *string, char output[33]) { - MD5_to_str(MD5_from_cstring(string), output); -} -/// Output in binary -static void MD5_String2binary (const char *string, uint8_t output[16]) __attribute__((deprecated)); -static inline void MD5_String2binary (const char *string, uint8_t output[16]) { - MD5_to_bin(MD5_from_cstring(string), output); -} - // statically-allocated output // whoever wrote this fails basic understanding of const char *MD5_saltcrypt(const char *key, const char *salt); @@ -61,4 +49,4 @@ bool pass_ok(const char *password, const char *crypted); /// This returns an in_addr because it is configurable whether it gets called at all struct in_addr MD5_ip(char *secret, struct in_addr ip); -#endif +#endif // MD5CALC_HPP diff --git a/src/common/mmo.hpp b/src/common/mmo.hpp index 151fa03..32d4285 100644 --- a/src/common/mmo.hpp +++ b/src/common/mmo.hpp @@ -2,39 +2,31 @@ #ifndef MMO_HPP #define MMO_HPP -# include <time.h> -# include "utils.hpp" // LCCWIN32 - -# define FIFOSIZE_SERVERLINK 256*1024 - -// set to 0 to not check IP of player between each server. -// set to another value if you want to check (1) -# define CMP_AUTHFIFO_IP 1 - -# define CMP_AUTHFIFO_LOGIN2 1 - -# define MAX_MAP_PER_SERVER 512 -# define MAX_INVENTORY 100 -# define MAX_AMOUNT 30000 -# define MAX_ZENY 1000000000 // 1G zeny -# define MAX_CART 100 -# define MAX_SKILL 450 -# define GLOBAL_REG_NUM 96 -# define ACCOUNT_REG_NUM 16 -# define ACCOUNT_REG2_NUM 16 -# define DEFAULT_WALK_SPEED 150 -# define MIN_WALK_SPEED 0 -# define MAX_WALK_SPEED 1000 -# define MAX_STORAGE 300 -# define MAX_GUILD_STORAGE 1000 -# define MAX_PARTY 12 -# define MAX_GUILD 120 // increased max guild members to accomodate for +2 increase for extension levels [Valaris] (removed) [PoW] -# define MAX_GUILDPOSITION 20 // increased max guild positions to accomodate for all members [Valaris] (removed) [PoW] -# define MAX_GUILDEXPLUSION 32 -# define MAX_GUILDALLIANCE 16 -# define MAX_GUILDSKILL 8 -# define MAX_GUILDCASTLE 24 // increased to include novice castles [Valaris] -# define MAX_GUILDLEVEL 50 +# include "sanity.hpp" +# include "timer.t.hpp" +# include "utils.hpp" + +constexpr int FIFOSIZE_SERVERLINK = 256 * 1024; + +constexpr int MAX_MAP_PER_SERVER = 512; +constexpr int MAX_INVENTORY = 100; +constexpr int MAX_AMOUNT = 30000; +constexpr int MAX_ZENY = 1000000000; // 1G zeny +constexpr int MAX_CART = 100; + +enum class SkillID : uint16_t; +constexpr SkillID MAX_SKILL = SkillID(474); // not 450 +constexpr SkillID get_enum_min_value(SkillID) { return SkillID(); } +constexpr SkillID get_enum_max_value(SkillID) { return MAX_SKILL; } + +constexpr int GLOBAL_REG_NUM = 96; +constexpr int ACCOUNT_REG_NUM = 16; +constexpr int ACCOUNT_REG2_NUM = 16; +constexpr interval_t DEFAULT_WALK_SPEED = std::chrono::milliseconds(150); +constexpr interval_t MIN_WALK_SPEED = interval_t::zero(); +constexpr interval_t MAX_WALK_SPEED = std::chrono::seconds(1); +constexpr int MAX_STORAGE = 300; +constexpr int MAX_PARTY = 12; # define MIN_HAIR_STYLE battle_config.min_hair_style # define MAX_HAIR_STYLE battle_config.max_hair_style @@ -43,29 +35,43 @@ # define MIN_CLOTH_COLOR battle_config.min_cloth_color # define MAX_CLOTH_COLOR battle_config.max_cloth_color -// for produce -# define MIN_ATTRIBUTE 0 -# define MAX_ATTRIBUTE 4 -# define ATTRIBUTE_NORMAL 0 -# define MIN_STAR 0 -# define MAX_STAR 3 +# define CHAR_CONF_NAME "conf/char_athena.conf" -# define MIN_PORTAL_MEMO 0 -# define MAX_PORTAL_MEMO 2 +namespace e +{ +enum class EPOS : uint16_t +{ + ZERO = 0x0000, -# define MAX_STATUS_TYPE 5 + LEGS = 0x0001, + WEAPON = 0x0002, + GLOVES = 0x0004, + CAPE = 0x0008, + MISC1 = 0x0010, + SHIELD = 0x0020, + SHOES = 0x0040, + MISC2 = 0x0080, + HAT = 0x0100, + TORSO = 0x0200, -# define CHAR_CONF_NAME "conf/char_athena.conf" + ARROW = 0x8000, +}; +ENUM_BITWISE_OPERATORS(EPOS) + +constexpr EPOS get_enum_min_value(EPOS) { return EPOS(0x0000); } +constexpr EPOS get_enum_max_value(EPOS) { return EPOS(0xffff); } +} +using e::EPOS; struct item { - int id; + int id; short nameid; short amount; - unsigned short equip; - char identify; - char refine; - char attribute; + EPOS equip; + uint8_t identify; + uint8_t refine; + uint8_t attribute; short card[4]; short broken; }; @@ -76,38 +82,100 @@ struct point short x, y; }; -struct skill +namespace e +{ +enum class SkillFlags : uint16_t; +} +using e::SkillFlags; + +struct skill_value { - unsigned short id, lv, flags; + unsigned short lv; + SkillFlags flags; }; struct global_reg { char str[32]; - int value; + int value; +}; + +// Option and Opt1..3 in map.hpp +namespace e +{ +enum class Option : uint16_t; +constexpr Option get_enum_min_value(Option) { return Option(0x0000); } +constexpr Option get_enum_max_value(Option) { return Option(0xffff); } +} +using e::Option; + +enum class ATTR +{ + STR = 0, + AGI = 1, + VIT = 2, + INT = 3, + DEX = 4, + LUK = 5, + + COUNT = 6, +}; + +constexpr ATTR ATTRs[6] = +{ + ATTR::STR, + ATTR::AGI, + ATTR::VIT, + ATTR::INT, + ATTR::DEX, + ATTR::LUK, +}; + +enum class ItemLook : uint16_t +{ + NONE = 0, + BLADE = 1, // or some other common weapons + _2, + SETZER_AND_SCYTHE = 3, + _6, + STAFF = 10, + BOW = 11, + _13 = 13, + _14 = 14, + _16 = 16, + SINGLE_HANDED_COUNT = 17, + + DUAL_BLADE = 0x11, + DUAL_2 = 0x12, + DUAL_6 = 0x13, + DUAL_12 = 0x14, + DUAL_16 = 0x15, + DUAL_26 = 0x16, }; struct mmo_charstatus { - int char_id; - int account_id; - int partner_id; + int char_id; + int account_id; + int partner_id; - int base_exp, job_exp, zeny; + int base_exp, job_exp, zeny; - short pc_class; + short species; short status_point, skill_point; - int hp, max_hp, sp, max_sp; - short option, karma, manner; + int hp, max_hp, sp, max_sp; + Option option; + short karma, manner; short hair, hair_color, clothes_color; - int party_id, guild_id; + int party_id; - short weapon, shield; + ItemLook weapon; + short shield; short head_top, head_mid, head_bottom; char name[24]; unsigned char base_level, job_level; - short str, agi, vit, int_, dex, luk; + earray<short, ATTR, ATTR::COUNT> attrs; unsigned char char_num, sex; unsigned long mapip; @@ -115,169 +183,53 @@ struct mmo_charstatus struct point last_point, save_point, memo_point[10]; struct item inventory[MAX_INVENTORY], cart[MAX_CART]; - struct skill skill[MAX_SKILL]; - int global_reg_num; + earray<skill_value, SkillID, MAX_SKILL> skill; + int global_reg_num; struct global_reg global_reg[GLOBAL_REG_NUM]; - int account_reg_num; + int account_reg_num; struct global_reg account_reg[ACCOUNT_REG_NUM]; - int account_reg2_num; + int account_reg2_num; struct global_reg account_reg2[ACCOUNT_REG2_NUM]; }; struct storage { - int dirty; - int account_id; + int dirty; + int account_id; short storage_status; short storage_amount; struct item storage_[MAX_STORAGE]; }; -struct guild_storage -{ - int dirty; - int guild_id; - short storage_status; - short storage_amount; - struct item storage_[MAX_GUILD_STORAGE]; -}; - struct map_session_data; struct gm_account { - int account_id; - int level; + int account_id; + int level; }; struct party_member { - int account_id; + int account_id; char name[24], map[24]; - int leader, online, lv; + int leader, online, lv; struct map_session_data *sd; }; struct party { - int party_id; + int party_id; char name[24]; - int exp; - int item; + int exp; + int item; struct party_member member[MAX_PARTY]; }; -struct guild_member -{ - int account_id, char_id; - short hair, hair_color, gender, pc_class, lv; - int exp, exp_payper; - short online, position; - int rsv1, rsv2; - char name[24]; - struct map_session_data *sd; -}; - -struct guild_position -{ - char name[24]; - int mode; - int exp_mode; -}; - -struct GuildAlliance -{ - int opposition; - int guild_id; - char name[24]; -}; - -struct GuildExpulsion -{ - char name[24]; - char mes[40]; - char acc[40]; - int account_id; - int rsv1, rsv2, rsv3; -}; - -struct guild_skill -{ - int id, lv; -}; - -struct guild -{ - int guild_id; - short guild_lv, connect_member, max_member, average_lv; - int exp, next_exp, skill_point, castle_id; - char name[24], master[24]; - struct guild_member member[MAX_GUILD]; - struct guild_position position[MAX_GUILDPOSITION]; - char mes1[60], mes2[120]; - int emblem_len, emblem_id; - char emblem_data[2048]; - GuildAlliance alliance[MAX_GUILDALLIANCE]; - GuildExpulsion explusion[MAX_GUILDEXPLUSION]; - struct guild_skill skill[MAX_GUILDSKILL]; -}; - -struct guild_castle -{ - int castle_id; - char map_name[24]; - char castle_name[24]; - char castle_event[24]; - int guild_id; - int economy; - int defense; - int triggerE; - int triggerD; - int nextTime; - int payTime; - int createTime; - int visibleC; - int visibleG0; - int visibleG1; - int visibleG2; - int visibleG3; - int visibleG4; - int visibleG5; - int visibleG6; - int visibleG7; - int Ghp0; // added Guardian HP [Valaris] - int Ghp1; - int Ghp2; - int Ghp3; - int Ghp4; - int Ghp5; - int Ghp6; - int Ghp7; - int GID0; - int GID1; - int GID2; - int GID3; - int GID4; - int GID5; - int GID6; - int GID7; // end addition [Valaris] -}; struct square { - int val1[5]; - int val2[5]; -}; - -enum -{ - GBI_EXP = 1, // ギルドのEXP - GBI_GUILDLV = 2, // ギルドのLv - GBI_SKILLPOINT = 3, // ギルドのスキルポイント - GBI_SKILLLV = 4, // ギルドスキルLv - - GMI_POSITION = 0, // メンバーの役職変更 - GMI_EXP = 1, // メンバーのEXP - + int val1[5]; + int val2[5]; }; #endif // MMO_HPP diff --git a/src/common/mt_rand.cpp b/src/common/mt_rand.cpp deleted file mode 100644 index 676eca1..0000000 --- a/src/common/mt_rand.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/* -// This is the ``Mersenne Twister'' random number generator MT19937, which -// generates pseudorandom integers uniformly distributed in 0..(2^32 - 1) -// starting from any odd seed in 0..(2^32 - 1). This version is a recode -// by Shawn Cokus (Cokus@math.washington.edu) on March 8, 1998 of a version by -// Takuji Nishimura (who had suggestions from Topher Cooper and Marc Rieffel in -// July-August 1997). -// -// Effectiveness of the recoding (on Goedel2.math.washington.edu, a DEC Alpha -// running OSF/1) using GCC -O3 as a compiler: before recoding: 51.6 sec. to -// generate 300 million random numbers; after recoding: 24.0 sec. for the same -// (i.e., 46.5% of original time), so speed is now about 12.5 million random -// number generations per second on this machine. -// -// According to the URL <http://www.math.keio.ac.jp/~matumoto/emt.html> -// (and paraphrasing a bit in places), the Mersenne Twister is ``designed -// with consideration of the flaws of various existing generators,'' has -// a period of 2^19937 - 1, gives a sequence that is 623-dimensionally -// equidistributed, and ``has passed many stringent tests, including the -// die-hard test of G. Marsaglia and the load test of P. Hellekalek and -// S. Wegenkittl.'' It is efficient in memory usage (typically using 2506 -// to 5012 bytes of static data, depending on data type sizes, and the code -// is quite short as well). It generates random numbers in batches of 624 -// at a time, so the caching and pipelining of modern systems is exploited. -// It is also divide- and mod-free. -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Library General Public License as published by -// the Free Software Foundation (either version 2 of the License or, at your -// option, any later version). This library is distributed in the hope that -// it will be useful, but WITHOUT ANY WARRANTY, without even the implied -// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See -// the GNU Library General Public License for more details. You should have -// received a copy of the GNU Library General Public License along with this -// library; if not, write to the Free Software Foundation, Inc., 59 Temple -// Place, Suite 330, Boston, MA 02111-1307, USA. -// -// The code as Shawn received it included the following notice: -// -// Copyright (C) 1997 Makoto Matsumoto and Takuji Nishimura. When -// you use this, send an e-mail to <matumoto@math.keio.ac.jp> with -// an appropriate reference to your work. -// -// It would be nice to CC: <Cokus@math.washington.edu> when you write. -// -*/ - -#include <time.h> -#include "mt_rand.hpp" - -#define N 624 // length of state vector -#define M 397 // a period parameter -#define K 0x9908B0DFU // a magic constant - -#define hiBit(u) ((u) & 0x80000000U) // mask all but highest bit of u -#define loBit(u) ((u) & 0x00000001U) // mask all but lowest bit of u -#define loBits(u) ((u) & 0x7FFFFFFFU) // mask the highest bit of u -#define mixBits(u, v) (hiBit(u)|loBits(v)) // move hi bit of u to hi bit of v - -static uint32_t state[N+1]; // state vector the +1 is needed due to the coding -static uint32_t *next; // next random value is computed from here -static int left = -1; // can *next++ this many times before reloading - -void mt_seed (uint32_t seed) -{ - uint32_t x = seed | 1U; - uint32_t *s = state; - left = 0; - - for (int j = N; *s++ = x, --j; x *= 69069U); -} - -static -void mt_reload (void) -{ - // if mt_seed has never been called - if (left < -1) - mt_seed (time (NULL)); - - // conceptually, these are indices into the state that wrap - uint32_t *p0 = state; - uint32_t *p2 = state + 2; - uint32_t *pM = state + M; - - uint32_t s0 = state[0]; - uint32_t s1 = state[1]; - - // regenerate the lower N-M elements of the state - for (int j = N-M+1; --j != 0; s0 = s1, s1 = *p2++) - *p0++ = *pM++ ^ (mixBits (s0, s1) >> 1) ^ (loBit (s1) ? K : 0U); - - pM = state; - // regenerate the next M-1 elements of the state - // note that s1 is set to state[N] at the end, but discarded - for (int j = M; --j != 0; s0 = s1, s1 = *p2++) - *p0++ = *pM++ ^ (mixBits (s0, s1) >> 1) ^ (loBit (s1) ? K : 0U); - - // regenerate the last 1 element of the state - s1 = state[0]; - *p0 = *pM ^ (mixBits (s0, s1) >> 1) ^ (loBit (s1) ? K : 0U); - - // ready for the normal mt_random algorithm - left = N; - next = state; -} - -uint32_t mt_random (void) -{ - if (--left < 0) - mt_reload (); - - uint32_t y = *next++; - y ^= (y >> 11); - y ^= (y << 7) & 0x9D2C5680U; - y ^= (y << 15) & 0xEFC60000U; - return y ^ (y >> 18); -} diff --git a/src/common/mt_rand.hpp b/src/common/mt_rand.hpp deleted file mode 100644 index c7bae4e..0000000 --- a/src/common/mt_rand.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef MT_RAND_HPP -#define MT_RAND_HPP - -# include "sanity.hpp" - -/// Initialize the generator (called automatically with time() if you don't) -void mt_seed (uint32_t seed); -/// Get a random number -uint32_t mt_random (void); - -/** - * ModuloRand and ModuloPlusRand - * These macros are used to replace the vast number of calls to rand()%mod - * TODO eliminate the rest of the calls to rand() - * MRAND(10) returns 0..9 - * MPRAND(5,10) returns 5..14 - */ -// The cast is essential because the result is sometimes -// compared with a possibly negative number. -// Because it's using modulus, high numbers shouldn't happen anyway. -# define MRAND(mod) ((int)(mt_random() % (mod))) -# define MPRAND(add, mod) ((add) + MRAND(mod)) - -#endif // MT_RAND_HPP diff --git a/src/common/nullpo.cpp b/src/common/nullpo.cpp index 67c839f..423ed8c 100644 --- a/src/common/nullpo.cpp +++ b/src/common/nullpo.cpp @@ -1,71 +1,28 @@ -#include <stdio.h> -#include <stdarg.h> -#include <string.h> #include "nullpo.hpp" -static void nullpo_info_core (const char *file, int line, const char *func); -__attribute__((format(printf, 4, 0))) -static void nullpo_info_core (const char *file, int line, const char *func, - const char *fmt, va_list ap); +#include <cstdio> -/// Null check and print format -bool nullpo_chk_f (const char *file, int line, const char *func, - const void *target, const char *fmt, ...) -{ - va_list ap; - - if (target) - return 0; - - va_start (ap, fmt); - nullpo_info_core (file, line, func, fmt, ap); - va_end (ap); - return 1; -} -bool nullpo_chk (const char *file, int line, const char *func, - const void *target) -{ - if (target) - return 0; - - nullpo_info_core (file, line, func); - return 1; -} - -/// External functions -void nullpo_info_f (const char *file, int line, const char *func, - const char *fmt, ...) -{ - va_list ap; - - va_start (ap, fmt); - nullpo_info_core (file, line, func, fmt, ap); - va_end (ap); -} -void nullpo_info (const char *file, int line, const char *func) -{ - nullpo_info_core (file, line, func); -} +#include "../poison.hpp" /// Actual output function -static void nullpo_info_core (const char *file, int line, const char *func) +static +void nullpo_info(const char *file, int line, const char *func) { if (!file) file = "??"; if (!func || !*func) func = "unknown"; - fprintf (stderr, "%s:%d: in func `%s': NULL pointer\n", file, line, func); + fprintf(stderr, "%s:%d: in func `%s': NULL pointer\n", + file, line, func); } -static void nullpo_info_core (const char *file, int line, const char *func, - const char *fmt, va_list ap) +bool nullpo_chk(const char *file, int line, const char *func, + const void *target) { - nullpo_info_core(file, line, func); - if (fmt && *fmt) - { - vfprintf (stderr, fmt, ap); - if (fmt[strlen (fmt) - 1] != '\n') - fputc('\n', stderr); - } + if (target) + return 0; + + nullpo_info(file, line, func); + return 1; } diff --git a/src/common/nullpo.hpp b/src/common/nullpo.hpp index 7aff691..305448f 100644 --- a/src/common/nullpo.hpp +++ b/src/common/nullpo.hpp @@ -1,61 +1,30 @@ /// return wrappers for unexpected NULL pointers #ifndef NULLPO_HPP #define NULLPO_HPP -/// Comment this out to live dangerously -# define NULLPO_CHECK +/// Uncomment this to live dangerously +/// (really exist to detect mostly-unused variables) +//# define BUG_FREE /// All functions print to standard error (was: standard output) /// nullpo_ret(cond) - return 0 if given pointer is NULL /// nullpo_retv(cond) - just return (function returns void) /// nullpo_retr(rv, cond) - return given value instead -/// the _f variants take a printf-format string and arguments -# ifdef NULLPO_CHECK -# define NLP_MARK __FILE__, __LINE__, __func__ -# define nullpo_ret(t) \ - if (nullpo_chk(NLP_MARK, t)) \ - return 0; -# define nullpo_retv(t) \ - if (nullpo_chk(NLP_MARK, t)) \ - return; +# ifndef BUG_FREE # define nullpo_retr(ret, t) \ - if (nullpo_chk(NLP_MARK, t)) \ + if (nullpo_chk(__FILE__, __LINE__, __PRETTY_FUNCTION__, t)) \ return ret; -# define nullpo_ret_f(t, fmt, ...) \ - if (nullpo_chk_f(NLP_MARK, t, fmt, ##__VA_ARGS__)) \ - return 0; -# define nullpo_retv_f(t, fmt, ...) \ - if (nullpo_chk_f(NLP_MARK, t, fmt, ##__VA_ARGS__)) \ - return; -# define nullpo_retr_f(ret, t, fmt, ...) \ - if (nullpo_chk_f(NLP_MARK, t, fmt, ##__VA_ARGS__)) \ - return ret; -# else // NULLPO_CHECK -# define nullpo_ret(t) t; -# define nullpo_retv(t) t; -# define nullpo_retr(ret, t) t; -# define nullpo_ret_f(t, fmt, ...) t; -# define nullpo_retv_f(t, fmt, ...) t; -# define nullpo_retr_f(ret, t, fmt, ...) t; -# endif // NULLPO_CHECK +# else // BUG_FREE +# define nullpo_retr(ret, t) /*t*/ +# endif // BUG_FREE -# include "sanity.hpp" +# define nullpo_ret(t) nullpo_retr(0, t) +# define nullpo_retv(t) nullpo_retr(, t) -/// Used by macros in this header -bool nullpo_chk (const char *file, int line, const char *func, - const void *target); +# include "sanity.hpp" /// Used by macros in this header -bool nullpo_chk_f (const char *file, int line, const char *func, - const void *target, const char *fmt, ...) - __attribute__ ((format (printf, 5, 6))); - -/// Used only by map/battle.c -void nullpo_info (const char *file, int line, const char *func); - -/// Not used -void nullpo_info_f (const char *file, int line, const char *func, - const char *fmt, ...) - __attribute__ ((format (printf, 4, 5))); +bool nullpo_chk(const char *file, int line, const char *func, + const void *target); #endif // NULLPO_HPP diff --git a/src/common/operators.hpp b/src/common/operators.hpp new file mode 100644 index 0000000..3d44b81 --- /dev/null +++ b/src/common/operators.hpp @@ -0,0 +1,47 @@ +#ifndef OPERATORS_HPP +#define OPERATORS_HPP + +namespace _operators +{ + class Comparable {}; + + template<class T> + bool operator == (T l, T r) + { + return l.value == r.value; + } + + template<class T> + bool operator != (T l, T r) + { + return l.value != r.value; + } + + template<class T> + bool operator < (T l, T r) + { + return l.value < r.value; + } + + template<class T> + bool operator <= (T l, T r) + { + return l.value <= r.value; + } + + template<class T> + bool operator > (T l, T r) + { + return l.value > r.value; + } + + template<class T> + bool operator >= (T l, T r) + { + return l.value >= r.value; + } +} + +using _operators::Comparable; + +#endif // OPERATORS_HPP diff --git a/src/common/random.cpp b/src/common/random.cpp new file mode 100644 index 0000000..273dcec --- /dev/null +++ b/src/common/random.cpp @@ -0,0 +1,8 @@ +#include "random2.hpp" + +#include "../poison.hpp" + +namespace random_ +{ + std::mt19937 generate{std::random_device()()}; +} // namespace random_ diff --git a/src/common/random.hpp b/src/common/random.hpp new file mode 100644 index 0000000..44057ed --- /dev/null +++ b/src/common/random.hpp @@ -0,0 +1,69 @@ +#ifndef RANDOM_HPP +#define RANDOM_HPP + +# include "random.t.hpp" + +# include "sanity.hpp" + +# include <random> + +// This is not namespace random since that collides with a C function, +// but this can be revisited when everything goes into namespace tmwa. +namespace random_ +{ + /// Get a random number from 0 .. 2**32 - 1 + extern std::mt19937 generate; + + /// Get a random number from 0 .. bound - 1 + inline + int to(int bound) + { + std::uniform_int_distribution<int> dist(0, bound - 1); + return dist(generate); + } + + /// Get a random number from low .. high (inclusive!) + inline + int in(int low, int high) + { + std::uniform_int_distribution<int> dist(low, high); + return dist(generate); + } + + inline + bool coin() + { + // sigh, can't specify <bool> directly ... + std::uniform_int_distribution<int> dist(false, true); + return dist(generate); + } + + inline + bool chance(Fraction f) + { + if (f.num <= 0) + return false; + if (f.num >= f.den) + return true; + return random_::to(f.den) < f.num; + } + + // C is usually one of: + // std::vector<T> + // std::initializer_list<T> + // std::array<T, n> + template<class C> + auto choice(C&& c) -> decltype(*c.begin()) + { + return *(c.begin() + random_::to(c.size())); + } + + // allow bare braces + template<class T> + T choice(std::initializer_list<T>&& il) + { + return random_::choice(il); + } +} // namespace random_ + +#endif // RANDOM_HPP diff --git a/src/common/random.t.hpp b/src/common/random.t.hpp new file mode 100644 index 0000000..98a6c59 --- /dev/null +++ b/src/common/random.t.hpp @@ -0,0 +1,23 @@ +#ifndef RANDOM_T_HPP +#define RANDOM_T_HPP + +namespace random_ +{ + struct Fraction + { + int num, den; + }; + + template<class T, T den> + struct Fixed + { + T num; + + operator Fraction() + { + return {num, den}; + } + }; +} // namespace random_ + +#endif // RANDOM_T_HPP diff --git a/src/common/random2.hpp b/src/common/random2.hpp new file mode 100644 index 0000000..86deddf --- /dev/null +++ b/src/common/random2.hpp @@ -0,0 +1,74 @@ +#ifndef RANDOM2_HPP +#define RANDOM2_HPP + +# include "random.hpp" +# include "utils2.hpp" + +# include <algorithm> + +namespace random_ +{ + namespace detail + { + struct RandomIterator + { + int bound; + int current; + bool frist; + + void operator ++() + { + frist = false; + // TODO: reimplement in terms of LFSRs, so that certain + // blockage patterns don't favor adjacent cells. + current = current + 1; + if (current == bound) + current = 0; + } + int operator *() + { + return current; + } + }; + + inline + bool operator == (RandomIterator l, RandomIterator r) + { + return l.current == r.current && l.frist == r.frist; + } + + inline + bool operator != (RandomIterator l, RandomIterator r) + { + return !(l == r); + } + } + + /// Yield every cell from 0 .. bound - 1 in some order. + /// The starting position is random, but not the order itself. + /// + /// Expected usage: + /// for (int i : random_::iterator(vec.size())) + /// if (vec[i].okay()) + /// return frob(vec[i]); + inline + IteratorPair<detail::RandomIterator> iterator(int bound) + { + int current = random_::to(bound); + return + { + detail::RandomIterator{bound, current, true}, + detail::RandomIterator{bound, current, false} + }; + } + + /// similar to std::random_shuffle(c.begin(), c.end()), but guaranteed + /// to use a good source of randomness. + template<class C> + void shuffle(C&& c) + { + std::random_shuffle(c.begin(), c.end(), random_::to); + } +} // namespace random_ + +#endif // RANDOM2_HPP diff --git a/src/common/sanity.hpp b/src/common/sanity.hpp index 7ffd077..e54739f 100644 --- a/src/common/sanity.hpp +++ b/src/common/sanity.hpp @@ -1,31 +1,54 @@ /// return wrappers for unexpected NULL pointers #ifndef SANITY_HPP #define SANITY_HPP + # ifndef __cplusplus # error "Please compile in C++ mode" -# endif -# if __GNUC__ < 3 -// I don't specifically know what version this requires, -// but GCC 3 was the beginning of modern GCC -# error "Please upgrade your compiler to at least GCC 3" -# endif -# ifndef __i386__ -// Known platform dependencies: -// endianness for the [RW]FIFO.* macros -// possibly, some signal-handling -# error "Unsupported platform" -# endif -# ifdef __x86_64__ -// I'm working on it - I know there are some pointer-size assumptions. -# error "Sorry, this code is believed not to be 64-bit safe" -# error "please compile with -m32" -# endif +# endif // __cplusplus + +# if __GNUC__ < 4 +# error "Your compiler is absolutely ancient. You have no chance ..." +# endif // __GNUC__ < 4 -/// A name for unused function arguments - can be repeated -# define UNUSED /* empty works for C++ */ /// Convert type assumptions to use the standard types here # include <cstdint> /// size_t, NULL # include <cstddef> +# if __GNUC__ == 4 +// clang identifies as GCC 4.2, but is mostly okay. +// Until a bug-free release of it happens, though, I won't recommend it. +// (patched) clang 3.1 would be the requirement +# if __GNUC_MINOR__ < 6 && !defined(__clang__) +# error "Please upgrade to at least GCC 4.6" +# endif // __GNUC_MINOR__ < 6 && !defined(__clang__) +// temporary workaround for library issues +// since __GLIBCXX__ is hard to use +# if __GNUC_MINOR__ == 6 +# define WORKAROUND_GCC46_COMPILER +# endif // __GNUC_MINOR__ == 6 +# ifdef __GLIBCXX__ +// versions of libstdc++ from gcc +// 4.6.0, 4.6.1, 4.6.2, 4.6.3 +# if __GLIBCXX__ == 20110325 \ + || __GLIBCXX__ == 20110627 \ + || __GLIBCXX__ == 20111026 \ + || __GLIBCXX__ == 20120301 \ + || __GLIBCXX__ == 20121127 // prerelease in Debian wheezy +# define WORKAROUND_GCC46_LIBRARY +# endif // __GLIBCXX__ == ... +# endif // defined __GLIBCXX__ +# if defined(WORKAROUND_GCC46_COMPILER) \ + && !defined(WORKAROUND_GCC46_LIBRARY) +# error "Unknown gcc 4.6.x release" +# endif // compiler and not library +# endif // __GNUC__ == 4 + +# if not defined(__i386__) and not defined(__x86_64__) +// Known platform dependencies: +// endianness for the [RW]FIFO.* macros +// possibly, some signal-handling +# error "Unsupported platform use x86 / amd64 only" +# endif // not __i386__ + #endif // SANITY_HPP diff --git a/src/common/socket.cpp b/src/common/socket.cpp index fab3804..2acbeb8 100644 --- a/src/common/socket.cpp +++ b/src/common/socket.cpp @@ -1,51 +1,74 @@ -// $Id: socket.c,v 1.1.1.1 2004/09/10 17:44:49 MagicalTux Exp $ -// original : core.c 2003/02/26 18:03:12 Rev 1.7 - -#include <stdio.h> -#include <stdlib.h> -#include <sys/types.h> -#include <errno.h> +#include "socket.hpp" -#include <sys/socket.h> -#include <netinet/in.h> +#include <arpa/inet.h> #include <netinet/tcp.h> -#include <sys/time.h> -#include <unistd.h> +#include <sys/socket.h> +//#include <sys/types.h> #include <fcntl.h> -#include <string.h> +#include <unistd.h> -#include "mmo.hpp" // [Valaris] thanks to fov -#include "socket.hpp" +#include <cstdlib> +#include <cstring> +#include <ctime> + +#include "cxxstdio.hpp" +//#include "mmo.hpp" #include "utils.hpp" +#include "../poison.hpp" + +static fd_set readfds; -int fd_max; -int currentuse; +int fd_max; +static +int currentuse; +static const uint32_t RFIFO_SIZE = 65536; +static const uint32_t WFIFO_SIZE = 65536; struct socket_data *session[FD_SETSIZE]; +/// clean up by discarding handled bytes +inline +void RFIFOFLUSH(int fd) +{ + memmove(session[fd]->rdata, RFIFOP(fd, 0), RFIFOREST(fd)); + session[fd]->rdata_size = RFIFOREST(fd); + session[fd]->rdata_pos = 0; +} + +/// how much room there is to read more data +inline +size_t RFIFOSPACE(int fd) +{ + return session[fd]->max_rdata - session[fd]->rdata_size; +} + + /// Discard all input -static void null_parse (int fd); +static +void null_parse(int fd); /// Default parser for new connections -static void (*default_func_parse) (int) = null_parse; +static +void(*default_func_parse)(int) = null_parse; -void set_defaultparse (void (*defaultparse) (int)) +void set_defaultparse(void(*defaultparse)(int)) { default_func_parse = defaultparse; } /// Read from socket to the queue -static void recv_to_fifo (int fd) +static +void recv_to_fifo(int fd) { if (session[fd]->eof) return; - ssize_t len = read (fd, session[fd]->rdata + session[fd]->rdata_size, - RFIFOSPACE (fd)); + ssize_t len = read(fd, session[fd]->rdata + session[fd]->rdata_size, + RFIFOSPACE(fd)); if (len > 0) { @@ -58,19 +81,20 @@ static void recv_to_fifo (int fd) } } -static void send_from_fifo (int fd) +static +void send_from_fifo(int fd) { if (session[fd]->eof) return; - ssize_t len = write (fd, session[fd]->wdata, session[fd]->wdata_size); + ssize_t len = write(fd, session[fd]->wdata, session[fd]->wdata_size); if (len > 0) { session[fd]->wdata_size -= len; if (session[fd]->wdata_size) { - memmove (session[fd]->wdata, session[fd]->wdata + len, + memmove(session[fd]->wdata, session[fd]->wdata + len, session[fd]->wdata_size); } session[fd]->connected = 1; @@ -81,32 +105,34 @@ static void send_from_fifo (int fd) } } -static void null_parse (int fd) +static +void null_parse(int fd) { - printf ("null_parse : %d\n", fd); - RFIFOSKIP (fd, RFIFOREST (fd)); + PRINTF("null_parse : %d\n", fd); + RFIFOSKIP(fd, RFIFOREST(fd)); } -static void connect_client (int listen_fd) +static +void connect_client(int listen_fd) { struct sockaddr_in client_address; - socklen_t len = sizeof (client_address); + socklen_t len = sizeof(client_address); - int fd = accept (listen_fd, (struct sockaddr *) &client_address, &len); + int fd = accept(listen_fd, (struct sockaddr *) &client_address, &len); if (fd == -1) { - perror ("accept"); + perror("accept"); return; } if (fd_max <= fd) { fd_max = fd + 1; } - if (!free_fds ()) + if (!free_fds()) { - fprintf (stderr, "softlimit reached, disconnecting : %d\n", fd); - delete_session (fd); + FPRINTF(stderr, "softlimit reached, disconnecting : %d\n", fd); + delete_session(fd); return; } @@ -114,11 +140,11 @@ static void connect_client (int listen_fd) /// Allow to bind() again after the server restarts. // Since the socket is still in the TIME_WAIT, there's a possibility // that formerly lost packets might be delivered and confuse the server. - setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); /// Send packets as soon as possible /// even if the kernel thinks there is too little for it to be worth it! /// Testing shows this is indeed a good idea. - setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes); + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes); // Linux-ism: Set socket options to optimize for thin streams // See http://lwn.net/Articles/308919/ and @@ -130,13 +156,13 @@ static void connect_client (int listen_fd) setsockopt(fd, IPPROTO_TCP, TCP_THIN_DUPACK, &yes, sizeof yes); #endif - FD_SET (fd, &readfds); + FD_SET(fd, &readfds); - fcntl (fd, F_SETFL, O_NONBLOCK); + fcntl(fd, F_SETFL, O_NONBLOCK); - CREATE (session[fd], struct socket_data, 1); - CREATE (session[fd]->rdata, uint8_t, RFIFO_SIZE); - CREATE (session[fd]->wdata, uint8_t, WFIFO_SIZE); + CREATE(session[fd], struct socket_data, 1); + CREATE(session[fd]->rdata, uint8_t, RFIFO_SIZE); + CREATE(session[fd]->wdata, uint8_t, WFIFO_SIZE); session[fd]->max_rdata = RFIFO_SIZE; session[fd]->max_wdata = WFIFO_SIZE; @@ -144,72 +170,72 @@ static void connect_client (int listen_fd) session[fd]->func_send = send_from_fifo; session[fd]->func_parse = default_func_parse; session[fd]->client_addr = client_address; - session[fd]->created = time (NULL); + session[fd]->created = TimeT::now(); session[fd]->connected = 0; currentuse++; } -int make_listen_port (uint16_t port) +int make_listen_port(uint16_t port) { struct sockaddr_in server_address; - int fd = socket (AF_INET, SOCK_STREAM, 0); + int fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { - perror ("socket"); + perror("socket"); return -1; } if (fd_max <= fd) fd_max = fd + 1; - fcntl (fd, F_SETFL, O_NONBLOCK); + fcntl(fd, F_SETFL, O_NONBLOCK); const int yes = 1; /// Allow to bind() again after the server restarts. // Since the socket is still in the TIME_WAIT, there's a possibility // that formerly lost packets might be delivered and confuse the server. - setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); /// Send packets as soon as possible /// even if the kernel thinks there is too little for it to be worth it! // I'm not convinced this is a good idea; although in minimizes the // latency for an individual write, it increases traffic in general. - setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes); + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes); server_address.sin_family = AF_INET; - server_address.sin_addr.s_addr = htonl (INADDR_ANY); - server_address.sin_port = htons (port); + server_address.sin_addr.s_addr = htonl(INADDR_ANY); + server_address.sin_port = htons(port); - if (bind (fd, (struct sockaddr *) &server_address, - sizeof (server_address)) == -1) + if (bind(fd, (struct sockaddr *) &server_address, + sizeof(server_address)) == -1) { - perror ("bind"); - exit (1); + perror("bind"); + exit(1); } - if (listen (fd, 5) == -1) + if (listen(fd, 5) == -1) { /* error */ - perror ("listen"); - exit (1); + perror("listen"); + exit(1); } - FD_SET (fd, &readfds); + FD_SET(fd, &readfds); - CREATE (session[fd], struct socket_data, 1); + CREATE(session[fd], struct socket_data, 1); session[fd]->func_recv = connect_client; - session[fd]->created = time (NULL); + session[fd]->created = TimeT::now(); session[fd]->connected = 1; currentuse++; return fd; } -int make_connection (uint32_t ip, uint16_t port) +int make_connection(uint32_t ip, uint16_t port) { struct sockaddr_in server_address; - int fd = socket (AF_INET, SOCK_STREAM, 0); + int fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { - perror ("socket"); + perror("socket"); return -1; } if (fd_max <= fd) @@ -219,43 +245,43 @@ int make_connection (uint32_t ip, uint16_t port) /// Allow to bind() again after the server restarts. // Since the socket is still in the TIME_WAIT, there's a possibility // that formerly lost packets might be delivered and confuse the server. - setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); /// Send packets as soon as possible /// even if the kernel thinks there is too little for it to be worth it! // I'm not convinced this is a good idea; although in minimizes the // latency for an individual write, it increases traffic in general. - setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes); + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes); server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = ip; - server_address.sin_port = htons (port); + server_address.sin_port = htons(port); - fcntl (fd, F_SETFL, O_NONBLOCK); + fcntl(fd, F_SETFL, O_NONBLOCK); /// Errors not caught - we must not block /// Let the main select() loop detect when we know the state - connect (fd, (struct sockaddr *) &server_address, - sizeof (struct sockaddr_in)); + connect(fd, (struct sockaddr *) &server_address, + sizeof(struct sockaddr_in)); - FD_SET (fd, &readfds); + FD_SET(fd, &readfds); - CREATE (session[fd], struct socket_data, 1); - CREATE (session[fd]->rdata, uint8_t, RFIFO_SIZE); - CREATE (session[fd]->wdata, uint8_t, WFIFO_SIZE); + CREATE(session[fd], struct socket_data, 1); + CREATE(session[fd]->rdata, uint8_t, RFIFO_SIZE); + CREATE(session[fd]->wdata, uint8_t, WFIFO_SIZE); session[fd]->max_rdata = RFIFO_SIZE; session[fd]->max_wdata = WFIFO_SIZE; session[fd]->func_recv = recv_to_fifo; session[fd]->func_send = send_from_fifo; session[fd]->func_parse = default_func_parse; - session[fd]->created = time (NULL); + session[fd]->created = TimeT::now(); session[fd]->connected = 1; currentuse++; return fd; } -void delete_session (int fd) +void delete_session(int fd) { if (fd < 0 || fd >= FD_SETSIZE) return; @@ -264,151 +290,155 @@ void delete_session (int fd) // but this is cheap and good enough for the typical case if (fd == fd_max - 1) fd_max--; - FD_CLR (fd, &readfds); + FD_CLR(fd, &readfds); if (session[fd]) { - free (session[fd]->rdata); - free (session[fd]->wdata); - free (session[fd]->session_data); - free (session[fd]); + free(session[fd]->rdata); + free(session[fd]->wdata); + free(session[fd]->session_data); + free(session[fd]); } session[fd] = NULL; // just close() would try to keep sending buffers - shutdown (fd, SHUT_RDWR); - close (fd); + shutdown(fd, SHUT_RDWR); + close(fd); currentuse--; if (currentuse < 0) { - fprintf (stderr, "delete_session: current sessions negative!\n"); + FPRINTF(stderr, "delete_session: current sessions negative!\n"); currentuse = 0; } return; } -void realloc_fifo (int fd, size_t rfifo_size, size_t wfifo_size) +void realloc_fifo(int fd, size_t rfifo_size, size_t wfifo_size) { struct socket_data *s = session[fd]; if (s->max_rdata != rfifo_size && s->rdata_size < rfifo_size) { - RECREATE (s->rdata, uint8_t, rfifo_size); + RECREATE(s->rdata, uint8_t, rfifo_size); s->max_rdata = rfifo_size; } if (s->max_wdata != wfifo_size && s->wdata_size < wfifo_size) { - RECREATE (s->wdata, uint8_t, wfifo_size); + RECREATE(s->wdata, uint8_t, wfifo_size); s->max_wdata = wfifo_size; } } -void WFIFOSET (int fd, size_t len) +void WFIFOSET(int fd, size_t len) { struct socket_data *s = session[fd]; if (s->wdata_size + len + 16384 > s->max_wdata) { - realloc_fifo (fd, s->max_rdata, s->max_wdata << 1); - printf ("socket: %d wdata expanded to %d bytes.\n", fd, s->max_wdata); + realloc_fifo(fd, s->max_rdata, s->max_wdata << 1); + PRINTF("socket: %d wdata expanded to %zu bytes.\n", fd, s->max_wdata); } if (s->wdata_size + len + 2048 < s->max_wdata) s->wdata_size += len; else - fprintf (stderr, "socket: %d wdata lost !!\n", fd), abort (); + FPRINTF(stderr, "socket: %d wdata lost !!\n", fd), abort(); } -void do_sendrecv (uint32_t next) +void do_sendrecv(interval_t next_ms) { fd_set rfd = readfds, wfd; - FD_ZERO (&wfd); + FD_ZERO(&wfd); for (int i = 0; i < fd_max; i++) { if (session[i] && session[i]->wdata_size) - FD_SET (i, &wfd); + FD_SET(i, &wfd); } struct timeval timeout; - timeout.tv_sec = next / 1000; - timeout.tv_usec = next % 1000 * 1000; - if (select (fd_max, &rfd, &wfd, NULL, &timeout) <= 0) + { + std::chrono::seconds next_s = std::chrono::duration_cast<std::chrono::seconds>(next_ms); + std::chrono::microseconds next_us = next_ms - next_s; + timeout.tv_sec = next_s.count(); + timeout.tv_usec = next_us.count(); + } + if (select(fd_max, &rfd, &wfd, NULL, &timeout) <= 0) return; for (int i = 0; i < fd_max; i++) { if (!session[i]) continue; - if (FD_ISSET (i, &wfd)) + if (FD_ISSET(i, &wfd)) { if (session[i]->func_send) //send_from_fifo(i); - session[i]->func_send (i); + session[i]->func_send(i); } - if (FD_ISSET (i, &rfd)) + if (FD_ISSET(i, &rfd)) { if (session[i]->func_recv) //recv_to_fifo(i); //or connect_client(i); - session[i]->func_recv (i); + session[i]->func_recv(i); } } } -void do_parsepacket (void) +void do_parsepacket(void) { for (int i = 0; i < fd_max; i++) { if (!session[i]) continue; if (!session[i]->connected - && time (NULL) - session[i]->created > CONNECT_TIMEOUT) + && static_cast<time_t>(TimeT::now()) - static_cast<time_t>(session[i]->created) > CONNECT_TIMEOUT) { - printf ("Session #%d timed out\n", i); + PRINTF("Session #%d timed out\n", i); session[i]->eof = 1; } if (!session[i]->rdata_size && !session[i]->eof) continue; if (session[i]->func_parse) { - session[i]->func_parse (i); + session[i]->func_parse(i); /// some func_parse may call delete_session if (!session[i]) continue; } /// Reclaim buffer space for what was read - RFIFOFLUSH (i); + RFIFOFLUSH(i); } } -void do_socket (void) +void do_socket(void) { - FD_ZERO (&readfds); + FD_ZERO(&readfds); currentuse = 3; } -void RFIFOSKIP (int fd, size_t len) +void RFIFOSKIP(int fd, size_t len) { struct socket_data *s = session[fd]; s->rdata_pos += len; if (s->rdata_size < s->rdata_pos) { - fprintf (stderr, "too many skip\n"); - abort (); + FPRINTF(stderr, "too many skip\n"); + abort(); } } -void fclose_ (FILE * fp) +void fclose_(FILE * fp) { - if (fclose (fp)) - perror ("fclose"), abort (); + if (fclose(fp)) + perror("fclose"), abort(); currentuse--; } -FILE *fopen_ (const char *path, const char *mode) +FILE *fopen_(const char *path, const char *mode) { - FILE *f = fopen (path, mode); + FILE *f = fopen(path, mode); if (f) currentuse++; return f; } -bool free_fds (void) +bool free_fds(void) { return currentuse < SOFT_LIMIT; } diff --git a/src/common/socket.hpp b/src/common/socket.hpp index 00f2df0..d7c6b9c 100644 --- a/src/common/socket.hpp +++ b/src/common/socket.hpp @@ -3,62 +3,19 @@ # include "sanity.hpp" -# include <stdio.h> - -# include <sys/types.h> -# include <sys/socket.h> # include <netinet/in.h> -# include <time.h> +# include <cstdio> -/// Check how much can be read -# define RFIFOREST(fd) (session[fd]->rdata_size-session[fd]->rdata_pos) -/// Read from the queue -# define RFIFOP(fd,pos) (session[fd]->rdata+session[fd]->rdata_pos+(pos)) -# define RFIFOB(fd,pos) (*(uint8_t*)(RFIFOP(fd, pos))) -# define RFIFOW(fd,pos) (*(uint16_t*)(RFIFOP(fd, pos))) -# define RFIFOL(fd,pos) (*(uint32_t*)(RFIFOP(fd, pos))) -/// Done reading -void RFIFOSKIP (int fd, size_t len); -/// Internal - clean up by discarding handled bytes -// Atm this is also called in char/char.c, but that is unnecessary -# define RFIFOFLUSH(fd) (memmove(session[fd]->rdata,RFIFOP(fd,0),RFIFOREST(fd)),\ -session[fd]->rdata_size=RFIFOREST(fd),\ -session[fd]->rdata_pos=0) - -/// Used internally - how much room there is to read more data -# define RFIFOSPACE(fd) (session[fd]->max_rdata-session[fd]->rdata_size) - -/// Read from an arbitrary buffer -# define RBUFP(p,pos) (((uint8_t*)(p))+(pos)) -# define RBUFB(p,pos) (*(uint8_t*)RBUFP((p),(pos))) -# define RBUFW(p,pos) (*(uint16_t*)RBUFP((p),(pos))) -# define RBUFL(p,pos) (*(uint32_t*)RBUFP((p),(pos))) - - - -/// Unused - check how much data can be written -# define WFIFOSPACE(fd) (session[fd]->max_wdata-session[fd]->wdata_size) -/// Write to the queue -# define WFIFOP(fd,pos) (session[fd]->wdata+session[fd]->wdata_size+(pos)) -# define WFIFOB(fd,pos) (*(uint8_t*)(WFIFOP(fd,pos))) -# define WFIFOW(fd,pos) (*(uint16_t*)(WFIFOP(fd,pos))) -# define WFIFOL(fd,pos) (*(uint32_t*)(WFIFOP(fd,pos))) -/// Finish writing -void WFIFOSET (int fd, size_t len); - -/// Write to an arbitrary buffer -#define WBUFP(p,pos) (((uint8_t*)(p))+(pos)) -#define WBUFB(p,pos) (*(uint8_t*)WBUFP((p),(pos))) -#define WBUFW(p,pos) (*(uint16_t*)WBUFP((p),(pos))) -#define WBUFL(p,pos) (*(uint32_t*)WBUFP((p),(pos))) +# include "utils.hpp" +# include "timer.t.hpp" // Struct declaration struct socket_data { /// Checks whether a newly-connected socket actually does anything - time_t created; + TimeT created; bool connected; /// Flag needed since structure must be freed in a server-dependent manner @@ -80,22 +37,22 @@ struct socket_data /// Only called when select() indicates the socket is ready /// If, after that, nothing is read, it sets eof // These could probably be hard-coded with a little work - void (*func_recv) (int); - void (*func_send) (int); + void(*func_recv)(int); + void(*func_send)(int); /// This is the important one /// Set to different functions depending on whether the connection /// is a player or a server/ladmin /// Can be set explicitly or via set_defaultparse - void (*func_parse) (int); + void(*func_parse)(int); /// Server-specific data type void *session_data; }; // save file descriptors for important stuff -# define SOFT_LIMIT (FD_SETSIZE - 50) +constexpr int SOFT_LIMIT = FD_SETSIZE - 50; // socket timeout to establish a full connection in seconds -# define CONNECT_TIMEOUT 15 +constexpr int CONNECT_TIMEOUT = 15; /// Everyone who has connected // note: call delete_session(i) to null out an element @@ -106,30 +63,139 @@ extern int fd_max; /// open a socket, bind, and listen. Return an fd, or -1 if socket() fails, /// but exit if bind() or listen() fails -int make_listen_port (uint16_t port); +int make_listen_port(uint16_t port); /// Connect to an address, return a connected socket or -1 // FIXME - this is IPv4 only! -int make_connection (uint32_t ip, uint16_t port); +int make_connection(uint32_t ip, uint16_t port); /// free() the structure and close() the fd -void delete_session (int); +void delete_session(int); /// Make a the internal queues bigger -void realloc_fifo (int fd, size_t rfifo_size, size_t wfifo_size); +void realloc_fifo(int fd, size_t rfifo_size, size_t wfifo_size); /// Update all sockets that can be read/written from the queues -void do_sendrecv (uint32_t next); +void do_sendrecv(interval_t next); /// Call the parser function for every socket that has read data -void do_parsepacket (void); +void do_parsepacket(void); /// An init function -void do_socket (void); +void do_socket(void); /// Change the default parser for newly connected clients // typically called once per server, but individual clients may identify // themselves as servers -void set_defaultparse (void (*defaultparse) (int)); +void set_defaultparse(void(*defaultparse)(int)); /// Wrappers to track number of free FDs -void fclose_ (FILE * fp); -FILE *fopen_ (const char *path, const char *mode); -bool free_fds (void); +void fclose_(FILE * fp); +FILE *fopen_(const char *path, const char *mode); + +bool free_fds(void); + + + +/// Check how much can be read +inline +size_t RFIFOREST(int fd) +{ + return session[fd]->rdata_size - session[fd]->rdata_pos; +} +/// Read from the queue +inline +const void *RFIFOP(int fd, size_t pos) +{ + return session[fd]->rdata + session[fd]->rdata_pos + pos; +} +inline +uint8_t RFIFOB(int fd, size_t pos) +{ + return *static_cast<const uint8_t *>(RFIFOP(fd, pos)); +} +inline +uint16_t RFIFOW(int fd, size_t pos) +{ + return *static_cast<const uint16_t *>(RFIFOP(fd, pos)); +} +inline +uint32_t RFIFOL(int fd, size_t pos) +{ + return *static_cast<const uint32_t *>(RFIFOP(fd, pos)); +} + +/// Done reading +void RFIFOSKIP(int fd, size_t len); + +/// Read from an arbitrary buffer +inline +const void *RBUFP(const uint8_t *p, size_t pos) +{ + return p + pos; +} +inline +uint8_t RBUFB(const uint8_t *p, size_t pos) +{ + return *static_cast<const uint8_t *>(RBUFP(p, pos)); +} +inline +uint16_t RBUFW(const uint8_t *p, size_t pos) +{ + return *static_cast<const uint16_t *>(RBUFP(p, pos)); +} +inline +uint32_t RBUFL(const uint8_t *p, size_t pos) +{ + return *static_cast<const uint32_t *>(RBUFP(p, pos)); +} + + +/// Unused - check how much data can be written +inline +size_t WFIFOSPACE(int fd) +{ + return session[fd]->max_wdata - session[fd]->wdata_size; +} +/// Write to the queue +inline +void *WFIFOP(int fd, size_t pos) +{ + return session[fd]->wdata + session[fd]->wdata_size + pos; +} +inline +uint8_t& WFIFOB(int fd, size_t pos) +{ + return *static_cast<uint8_t *>(WFIFOP(fd, pos)); +} +inline +uint16_t& WFIFOW(int fd, size_t pos) +{ + return *static_cast<uint16_t *>(WFIFOP(fd, pos)); +} +inline +uint32_t& WFIFOL(int fd, size_t pos) +{ + return *static_cast<uint32_t *>(WFIFOP(fd, pos)); +} +/// Finish writing +void WFIFOSET(int fd, size_t len); + +/// Write to an arbitrary buffer +inline +void *WBUFP(uint8_t *p, size_t pos) +{ + return p + pos; +} +inline +uint8_t& WBUFB(uint8_t *p, size_t pos) +{ + return *static_cast<uint8_t *>(WBUFP(p, pos)); +} +inline +uint16_t& WBUFW(uint8_t *p, size_t pos) +{ + return *static_cast<uint16_t *>(WBUFP(p, pos)); +} +inline +uint32_t& WBUFL(uint8_t *p, size_t pos) +{ + return *static_cast<uint32_t *>(WBUFP(p, pos)); +} #endif // SOCKET_HPP diff --git a/src/common/timer.cpp b/src/common/timer.cpp index 66aaa9b..7b115d9 100644 --- a/src/common/timer.cpp +++ b/src/common/timer.cpp @@ -1,257 +1,193 @@ -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/types.h> +#include "timer.hpp" -#include <sys/socket.h> +#include <sys/stat.h> #include <sys/time.h> -#include "timer.hpp" +#include <cassert> +#include <cstring> + +#include <queue> + +#include "cxxstdio.hpp" #include "utils.hpp" -static struct TimerData *timer_data; -static uint32_t timer_data_max, timer_data_num; -static timer_id *free_timer_list; -static uint32_t free_timer_list_max, free_timer_list_pos; +#include "../poison.hpp" + +struct TimerData +{ + /// This will be reset on call, to avoid problems. + Timer *owner; + + /// When it will be triggered + tick_t tick; + /// What will be done + timer_func func; + /// Repeat rate - 0 for oneshot + interval_t interval; + + TimerData(Timer *o, tick_t t, timer_func f, interval_t i) + : owner(o) + , tick(t) + , func(std::move(f)) + , interval(i) + {} +}; + +struct TimerCompare +{ + /// implement "less than" + bool operator() (TimerData *l, TimerData *r) + { + // C++ provides a max-heap, but we want + // the smallest tick to be the head (a min-heap). + return l->tick > r->tick; + } +}; -/// Okay, I think I understand this structure now: -/// the timer heap is a magic queue that allows inserting timers and then popping them in order -/// designed to copy only log2(N) entries instead of N -// timer_heap[0] is the size (greatest index into the heap) -// timer_heap[1] is the first actual element -// timer_heap_max increases 256 at a time and never decreases -static uint32_t timer_heap_max = 0; -/// FIXME: refactor the code to put the size in a separate variable -//nontrivial because indices get multiplied -static timer_id *timer_heap = NULL; +static +std::priority_queue<TimerData *, std::vector<TimerData *>, TimerCompare> timer_heap; -static uint32_t gettick_cache; -static uint8_t gettick_count = 0; +tick_t gettick_cache; -uint32_t gettick_nocache (void) +tick_t milli_clock::now(void) noexcept { struct timeval tval; // BUG: This will cause strange behavior if the system clock is changed! // it should be reimplemented in terms of clock_gettime(CLOCK_MONOTONIC, ) - gettimeofday (&tval, NULL); - gettick_count = 255; - return gettick_cache = tval.tv_sec * 1000 + tval.tv_usec / 1000; + gettimeofday(&tval, NULL); + return gettick_cache = tick_t(std::chrono::seconds(tval.tv_sec) + + std::chrono::duration_cast<std::chrono::milliseconds>( + std::chrono::microseconds(tval.tv_usec))); } -uint32_t gettick (void) +static +void do_nothing(TimerData *, tick_t) { - if (gettick_count--) - return gettick_cache; - return gettick_nocache (); } -static void push_timer_heap (timer_id index) +void Timer::cancel() { - if (timer_heap == NULL || timer_heap[0] + 1 >= timer_heap_max) - { - timer_heap_max += 256; - RECREATE (timer_heap, timer_id, timer_heap_max); - memset (timer_heap + (timer_heap_max - 256), 0, sizeof (timer_id) * 256); - } -// timer_heap[0] is the greatest index into the heap, which increases - timer_heap[0]++; - - timer_id h = timer_heap[0]-1, i = (h - 1) / 2; - while (h) - { - // avoid wraparound problems, it really means this: - // timer_data[index].tick >= timer_data[timer_heap[i+1]].tick - if ( DIFF_TICK(timer_data[index].tick, timer_data[timer_heap[i+1]].tick) >= 0) - break; - timer_heap[h + 1] = timer_heap[i + 1]; - h = i; - i = (h - 1) / 2; - } - timer_heap[h + 1] = index; + if (!td) + return; + + assert (this == td->owner); + td->owner = nullptr; + td->func = do_nothing; + td->interval = interval_t::zero(); + td = nullptr; } -static timer_id top_timer_heap (void) +void Timer::detach() { - if (!timer_heap || !timer_heap[0]) - return -1; - return timer_heap[1]; + assert (this == td->owner); + td->owner = nullptr; + td = nullptr; } -static timer_id pop_timer_heap (void) +static +void push_timer_heap(TimerData *td) { - if (!timer_heap || !timer_heap[0]) - return -1; - timer_id ret = timer_heap[1]; - timer_id last = timer_heap[timer_heap[0]]; - timer_heap[0]--; - - uint32_t h, k; - for (h = 0, k = 2; k < timer_heap[0]; k = k * 2 + 2) - { - if (DIFF_TICK(timer_data[timer_heap[k + 1]].tick, timer_data[timer_heap[k]].tick) > 0) - k--; - timer_heap[h + 1] = timer_heap[k + 1], h = k; - } - if (k == timer_heap[0]) - timer_heap[h + 1] = timer_heap[k], h = k - 1; - - uint32_t i = (h - 1) / 2; - while (h) - { - if (DIFF_TICK (timer_data[timer_heap[i + 1]].tick, timer_data[last].tick) <= 0) - break; - timer_heap[h + 1] = timer_heap[i + 1]; - h = i; - i = (h - 1) / 2; - } - timer_heap[h + 1] = last; + timer_heap.push(td); +} - return ret; +static +TimerData *top_timer_heap(void) +{ + if (timer_heap.empty()) + return nullptr; + return timer_heap.top(); } -timer_id add_timer (tick_t tick, timer_func func, custom_id_t id, custom_data_t data) +static +void pop_timer_heap(void) { - timer_id i; + timer_heap.pop(); +} - if (free_timer_list_pos) - { - // Retrieve a freed timer id instead of a new one - // I think it should be possible to avoid the loop somehow - do - { - i = free_timer_list[--free_timer_list_pos]; - } - while (i >= timer_data_num && free_timer_list_pos > 0); - } - else - i = timer_data_num; +Timer::Timer(tick_t tick, timer_func func, interval_t interval) +: td(new TimerData(this, tick, std::move(func), interval)) +{ + assert (interval >= interval_t::zero()); - // I have no idea what this is doing - if (i >= timer_data_num) - for (i = timer_data_num; i < timer_data_max && timer_data[i].type; i++) - ; - if (i >= timer_data_num && i >= timer_data_max) - { - if (timer_data_max == 0) - { - timer_data_max = 256; - CREATE (timer_data, struct TimerData, timer_data_max); - } - else - { - timer_data_max += 256; - RECREATE (timer_data, struct TimerData, timer_data_max); - memset (timer_data + (timer_data_max - 256), 0, - sizeof (struct TimerData) * 256); - } - } - timer_data[i].tick = tick; - timer_data[i].func = func; - timer_data[i].id = id; - timer_data[i].data = data; - timer_data[i].type = TIMER_ONCE_AUTODEL; - timer_data[i].interval = 1000; - push_timer_heap (i); - if (i >= timer_data_num) - timer_data_num = i + 1; - return i; + push_timer_heap(td); } -timer_id add_timer_interval (tick_t tick, timer_func func, custom_id_t id, - custom_data_t data, interval_t interval) +Timer::Timer(Timer&& t) +: td(t.td) { - timer_id tid = add_timer (tick, func, id, data); - timer_data[tid].type = TIMER_INTERVAL; - timer_data[tid].interval = interval; - return tid; + t.td = nullptr; + if (td) + { + assert (td->owner == &t); + td->owner = this; + } } -void delete_timer (timer_id id, timer_func func) +Timer& Timer::operator = (Timer&& t) { - if (id == 0 || id >= timer_data_num) + std::swap(td, t.td); + if (td) { - fprintf (stderr, "delete_timer error : no such timer %d\n", id); - abort (); + assert (td->owner == &t); + td->owner = this; } - if (timer_data[id].func != func) + if (t.td) { - fprintf (stderr, "Timer mismatch\n"); - abort (); + assert (t.td->owner == this); + t.td->owner = &t; } - // "to let them disappear" - is this just in case? - timer_data[id].func = NULL; - timer_data[id].type = TIMER_ONCE_AUTODEL; - timer_data[id].tick -= 60 * 60 * 1000; -} - -tick_t addtick_timer (timer_id tid, interval_t tick) -{ - return timer_data[tid].tick += tick; + return *this; } -struct TimerData *get_timer (timer_id tid) +interval_t do_timer(tick_t tick) { - return &timer_data[tid]; -} - -interval_t do_timer (tick_t tick) -{ - timer_id i; /// Number of milliseconds until it calls this again // this says to wait 1 sec if all timers get popped - interval_t nextmin = 1000; + interval_t nextmin = std::chrono::seconds(1); - while ((i = top_timer_heap ()) != (timer_id)-1) + while (TimerData *td = top_timer_heap()) { // while the heap is not empty and - if (DIFF_TICK (timer_data[i].tick, tick) > 0) + if (td->tick > tick) { /// Return the time until the next timer needs to goes off - nextmin = DIFF_TICK (timer_data[i].tick, tick); + nextmin = td->tick - tick; break; } - pop_timer_heap (); - if (timer_data[i].func) - { - if (DIFF_TICK (timer_data[i].tick, tick) < -1000) - { - // If we are too far past the requested tick, call with the current tick instead to fix reregistering problems - timer_data[i].func (i, tick, timer_data[i].id, timer_data[i].data); - } - else - { - timer_data[i].func (i, timer_data[i].tick, timer_data[i].id, timer_data[i].data); - } - } - switch (timer_data[i].type) + pop_timer_heap(); + + // Prevent destroying the object we're in. + // Note: this would be surprising in an interval timer, + // but all interval timers do an immediate explicit detach(). + if (td->owner) + td->owner->detach(); + // If we are too far past the requested tick, call with + // the current tick instead to fix reregistration problems + if (td->tick + std::chrono::seconds(1) < tick) + td->func(td, tick); + else + td->func(td, td->tick); + + if (td->interval == interval_t::zero()) { - case TIMER_ONCE_AUTODEL: - timer_data[i].type = TIMER_NONE; - if (free_timer_list_pos >= free_timer_list_max) - { - free_timer_list_max += 256; - RECREATE (free_timer_list, uint32_t, free_timer_list_max); - memset (free_timer_list + (free_timer_list_max - 256), - 0, 256 * sizeof (uint32_t)); - } - free_timer_list[free_timer_list_pos++] = i; - break; - case TIMER_INTERVAL: - if (DIFF_TICK (timer_data[i].tick, tick) < -1000) - { - timer_data[i].tick = tick + timer_data[i].interval; - } - else - { - timer_data[i].tick += timer_data[i].interval; - } - push_timer_heap (i); - break; + delete td; + continue; } + if (td->tick + std::chrono::seconds(1) < tick) + td->tick = tick + td->interval; + else + td->tick += td->interval; + push_timer_heap(td); } - if (nextmin < 10) - nextmin = 10; - return nextmin; + return std::max(nextmin, std::chrono::milliseconds(10)); +} + +tick_t file_modified(const char *name) +{ + struct stat buf; + if (stat(name, &buf)) + return tick_t(); + return tick_t(std::chrono::seconds(buf.st_mtime)); } diff --git a/src/common/timer.hpp b/src/common/timer.hpp index fdda344..c581377 100644 --- a/src/common/timer.hpp +++ b/src/common/timer.hpp @@ -1,61 +1,25 @@ #ifndef TIMER_HPP #define TIMER_HPP -# include "sanity.hpp" +# include "timer.t.hpp" -enum TIMER_TYPE -{ - TIMER_NONE, - TIMER_ONCE_AUTODEL, - TIMER_INTERVAL, -}; -/// This is needed to produce a signed result when 2 ticks are subtracted -# define DIFF_TICK(a,b) ((int32_t)((a)-(b))) +# include "sanity.hpp" -// TODO replace with signed 64-bit to make code more clear and protect from the future -typedef uint32_t tick_t; -typedef uint32_t interval_t; -typedef uint32_t timer_id; -// BUG: pointers are stored in here -typedef int32_t custom_id_t; -typedef int32_t custom_data_t; -typedef void (*timer_func) (timer_id, tick_t, custom_id_t, custom_data_t); +// updated automatically when using milli_clock::now() +// which is done only by core.cpp +extern tick_t gettick_cache; -struct TimerData +inline +tick_t gettick(void) { - /// When it will be triggered - tick_t tick; - /// What will be done - timer_func func; - /// Arbitrary data. WARNING, callers are stupid and put pointers in here - // Should we change to void* or intptr_t ? - custom_id_t id; - custom_data_t data; - /// Type of timer - 0 initially - enum TIMER_TYPE type; - /// Repeat rate - interval_t interval; -}; - -/// Server time, in milliseconds, since the epoch, -/// but use of 32-bit integers means it wraps every 49 days. -// The only external caller of this function is the core.c main loop, but that makes sense -// in fact, it might make more sense if gettick() ALWAYS returned that cached value -tick_t gettick_nocache (void); -/// This function is called enough that it's worth caching the result for -/// the next 255 times -tick_t gettick (void); - -timer_id add_timer (tick_t, timer_func, custom_id_t, custom_data_t); -timer_id add_timer_interval (tick_t, timer_func, custom_id_t, custom_data_t, interval_t); -void delete_timer (timer_id, timer_func); - -tick_t addtick_timer (timer_id, interval_t); -struct TimerData *get_timer (timer_id tid); - -/// Do all timers scheduled before tick, and return the number of milliseconds until the next timer happens -interval_t do_timer (tick_t tick); + return gettick_cache; +} +/// Do all timers scheduled before tick, and return the number of +/// milliseconds until the next timer happens +interval_t do_timer(tick_t tick); +/// Stat a file, and return its modification time, truncated to seconds. +tick_t file_modified(const char *name); #endif // TIMER_HPP diff --git a/src/common/timer.t.hpp b/src/common/timer.t.hpp new file mode 100644 index 0000000..ee9b5d2 --- /dev/null +++ b/src/common/timer.t.hpp @@ -0,0 +1,66 @@ +#ifndef TIMER_T_HPP +#define TIMER_T_HPP + +# include <chrono> +# include <functional> + +struct TimerData; + +/// An implementation of the C++ "clock" concept, exposing +/// durations in milliseconds. +class milli_clock +{ +public: + typedef std::chrono::milliseconds duration; + typedef duration::rep rep; + typedef duration::period period; + typedef std::chrono::time_point<milli_clock, duration> time_point; + static const bool is_steady = true; // assumed - not necessarily true + + static time_point now() noexcept; +}; + +/// A point in time. +typedef milli_clock::time_point tick_t; +/// The difference between two points in time. +typedef milli_clock::duration interval_t; +/// (to get additional arguments, use std::bind or a lambda). +typedef std::function<void (TimerData *, tick_t)> timer_func; + +class Timer +{ + friend struct TimerData; + TimerData *td; + + Timer(const Timer&) = delete; + Timer& operator = (const Timer&) = delete; +public: + /// Don't own anything yet. + Timer() : td(nullptr) {} + /// Schedule a timer for the given tick. + /// If you do not wish to keep track of it, call disconnect(). + /// Otherwise, you may cancel() or replace (operator =) it later. + /// + /// If the interval argument is given, the timer will reschedule + /// itself again forever. Otherwise, it will disconnect() itself + /// just BEFORE it is called. + Timer(tick_t tick, timer_func func, interval_t interval=interval_t::zero()); + + Timer(Timer&& t); + Timer& operator = (Timer&& t); + ~Timer() { cancel(); } + + /// Cancel the delivery of this timer's function, and make it falsy. + /// Implementation note: this doesn't actually remove it, just sets + /// the functor to do_nothing, and waits for the tick before removing. + void cancel(); + /// Make it falsy without cancelling the timer, + void detach(); + + /// Check if there is a timer connected. + explicit operator bool() { return td; } + /// Check if there is no connected timer. + bool operator !() { return !td; } +}; + +#endif // TIMER_T_HPP diff --git a/src/common/utils.cpp b/src/common/utils.cpp index fbdd87e..49dce34 100644 --- a/src/common/utils.cpp +++ b/src/common/utils.cpp @@ -1,18 +1,19 @@ #include "utils.hpp" -#include <cstring> -#include <cstdio> -#include <cstdlib> - #include <netinet/in.h> +#include <sys/time.h> + +#include <algorithm> + +#include "../poison.hpp" //----------------------------------------------------- // Function to suppress control characters in a string. //----------------------------------------------------- -int remove_control_chars (char *str) +int remove_control_chars(char *str) { - int i; - int change = 0; + int i; + int change = 0; for (i = 0; str[i]; i++) { @@ -29,39 +30,38 @@ int remove_control_chars (char *str) //--------------------------------------------------- // E-mail check: return 0 (not correct) or 1 (valid). //--------------------------------------------------- -int e_mail_check (const char *email) +int e_mail_check(const char *email) { char ch; const char *last_arobas; // athena limits - if (strlen (email) < 3 || strlen (email) > 39) + if (strlen(email) < 3 || strlen(email) > 39) return 0; // part of RFC limits (official reference of e-mail description) - if (strchr (email, '@') == NULL || email[strlen (email) - 1] == '@') + if (strchr(email, '@') == NULL || email[strlen(email) - 1] == '@') return 0; - if (email[strlen (email) - 1] == '.') + if (email[strlen(email) - 1] == '.') return 0; - last_arobas = strrchr (email, '@'); + last_arobas = strrchr(email, '@'); - if (strstr (last_arobas, "@.") != NULL || - strstr (last_arobas, "..") != NULL) + if (strstr(last_arobas, "@.") != NULL || + strstr(last_arobas, "..") != NULL) return 0; for (ch = 1; ch < 32; ch++) { - if (strchr (last_arobas, ch) != NULL) + if (strchr(last_arobas, ch) != NULL) { return 0; - break; } } - if (strchr (last_arobas, ' ') != NULL || - strchr (last_arobas, ';') != NULL) + if (strchr(last_arobas, ' ') != NULL || + strchr(last_arobas, ';') != NULL) return 0; // all correct @@ -74,15 +74,15 @@ int e_mail_check (const char *email) //------------------------------------------------- int config_switch (const char *str) { - if (strcasecmp (str, "on") == 0 || strcasecmp (str, "yes") == 0 - || strcasecmp (str, "oui") == 0 || strcasecmp (str, "ja") == 0 - || strcasecmp (str, "si") == 0) + if (strcasecmp(str, "on") == 0 || strcasecmp(str, "yes") == 0 + || strcasecmp(str, "oui") == 0 || strcasecmp(str, "ja") == 0 + || strcasecmp(str, "si") == 0) return 1; - if (strcasecmp (str, "off") == 0 || strcasecmp (str, "no") == 0 - || strcasecmp (str, "non") == 0 || strcasecmp (str, "nein") == 0) + if (strcasecmp(str, "off") == 0 || strcasecmp(str, "no") == 0 + || strcasecmp(str, "non") == 0 || strcasecmp(str, "nein") == 0) return 0; - return atoi (str); + return atoi(str); } const char *ip2str(struct in_addr ip, bool extra_dot) @@ -95,3 +95,62 @@ const char *ip2str(struct in_addr ip, bool extra_dot) sprintf(buf, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); return buf; } + +bool split_key_value(const std::string& line, std::string *w1, std::string *w2) +{ + std::string::const_iterator begin = line.begin(), end = line.end(); + + if (line[0] == '/' && line[1] == '/') + return false; + if (line.back() == '\r') + --end; + if (line.empty()) + return false; + + if (std::find_if(begin, end, + [](unsigned char c) { return c < ' '; } + ) != line.end()) + return false; + std::string::const_iterator colon = std::find(begin, end, ':'); + if (colon == end) + return false; + w1->assign(begin, colon); + ++colon; + while (std::isspace(*colon)) + ++colon; + w2->assign(colon, end); + return true; +} + +static_assert(sizeof(timestamp_seconds_buffer) == 20, "seconds buffer"); +static_assert(sizeof(timestamp_milliseconds_buffer) == 24, "millis buffer"); + +void stamp_time(timestamp_seconds_buffer& out, TimeT *t) +{ + struct tm when = t ? *t : TimeT::now(); + strftime(out, 20, "%Y-%m-%d %H:%M:%S", &when); +} +void stamp_time(timestamp_milliseconds_buffer& out) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + struct tm when = TimeT(tv.tv_sec); + strftime(out, 20, "%Y-%m-%d %H:%M:%S", &when); + sprintf(out + 19, ".%03d", int(tv.tv_usec / 1000)); +} + +void log_with_timestamp(FILE *out, const_string line) +{ + if (!line) + { + fputc('\n', out); + return; + } + timestamp_milliseconds_buffer tmpstr; + stamp_time(tmpstr); + fwrite(tmpstr, 1, sizeof(tmpstr), out); + fputs(": ", out); + fwrite(line.data(), 1, line.size(), out); + if (line.back() != '\n') + fputc('\n', out); +} diff --git a/src/common/utils.hpp b/src/common/utils.hpp index 1097bf7..33117ac 100644 --- a/src/common/utils.hpp +++ b/src/common/utils.hpp @@ -3,6 +3,15 @@ #include "sanity.hpp" +#include <cstdio> +#include <cstring> + +#include <string> + +#include "const_array.hpp" +#include "operators.hpp" +#include "utils2.hpp" + /* Notes about memory allocation in tmwAthena: There used to be 3 sources of allocation: these macros, @@ -10,17 +19,122 @@ a{C,M,Re}alloc in common/malloc.{h,c}, and direct calls. I deleted malloc.{h,c} because it was redundant; future calls should either use this or depend on the coming segfault. */ +template<class T> +void create_impl(T *& result, size_t number) +{ + result = (T *)calloc(number, sizeof(T)); + if (!result && number) + { + perror("SYSERR: malloc failure"); + abort(); + } +} +template<class T> +void recreate_impl(T *& result, size_t number) +{ + result = (T *)realloc(result, sizeof(T) * number); + if (!result && number) + { + perror("SYSERR: realloc failure"); + abort(); + } +} + # define CREATE(result, type, number) \ - if (!((result) = (type *) calloc ((number), sizeof(type)))) \ - { perror("SYSERR: malloc failure"); abort(); } else (void)0 + create_impl<type>(result, number) -# define RECREATE(result,type,number) \ - if (!((result) = (type *) realloc ((result), sizeof(type) * (number))))\ - { perror("SYSERR: realloc failure"); abort(); } else (void)0 +# define RECREATE(result, type, number) \ + recreate_impl<type>(result, number) -int remove_control_chars (char *str); -int e_mail_check (const char *email); +int remove_control_chars(char *str); +int e_mail_check(const char *email); int config_switch (const char *str); const char *ip2str(struct in_addr ip, bool extra_dot = false); +bool split_key_value(const std::string& line, std::string *w1, std::string *w2); + +inline +void strzcpy(char *dest, const char *src, size_t n) +{ + if (n) + { + strncpy(dest, src, n); + dest[n-1] = '\0'; + } +} + +// Exists in place of time_t, to give it a predictable printf-format. +// (on x86 and amd64, time_t == long, but not on x32) +static_assert(sizeof(long long) >= sizeof(time_t), "long long >= time_t"); +struct TimeT : Comparable +{ + long long value; + + // conversion + TimeT(time_t t=0) : value(t) {} + TimeT(struct tm t) : value(timegm(&t)) {} + operator time_t() { return value; } + operator struct tm() { time_t v = value; return *gmtime(&v); } + + explicit operator bool() { return value; } + bool operator !() { return !value; } + + // prevent surprises + template<class T> + TimeT(T) = delete; + template<class T> + operator T() = delete; + + static + TimeT now() + { + // poisoned, but this is still in header-land + return time(NULL); + } + + bool error() + { + return value == -1; + } + bool okay() + { + return !error(); + } +}; + +inline +long long convert_for_printf(TimeT t) +{ + return t.value; +} + +inline +long long& convert_for_scanf(TimeT& t) +{ + return t.value; +} + +typedef char timestamp_seconds_buffer[20]; +typedef char timestamp_milliseconds_buffer[24]; +void stamp_time(timestamp_seconds_buffer&, TimeT *t=nullptr); +void stamp_time(timestamp_milliseconds_buffer&); + +void log_with_timestamp(FILE *out, const_string line); + +#define TIMESTAMP_DUMMY "YYYY-MM-DD HH:MM:SS" +static_assert(sizeof(TIMESTAMP_DUMMY) == sizeof(timestamp_seconds_buffer), + "timestamp size"); +#define WITH_TIMESTAMP(str) str TIMESTAMP_DUMMY +// str: prefix: YYYY-MM-DD HH:MM:SS +// sizeof: 01234567890123456789012345678 +// str + sizeof: ^ +// -1: ^ +#define REPLACE_TIMESTAMP(str, t) \ + stamp_time( \ + reinterpret_cast<timestamp_seconds_buffer *>( \ + str + sizeof(str) \ + )[-1], \ + &t \ + ) + #endif //UTILS_HPP diff --git a/src/common/utils2.hpp b/src/common/utils2.hpp new file mode 100644 index 0000000..5f02beb --- /dev/null +++ b/src/common/utils2.hpp @@ -0,0 +1,230 @@ +#ifndef UTILS2_HPP +#define UTILS2_HPP + +#include "sanity.hpp" + +#include <functional> +#include <iterator> +#include <memory> +#include <type_traits> + +#ifdef __clang__ +# define FALLTHROUGH [[clang::fallthrough]] +#else +# define FALLTHROUGH /* fallthrough */ +#endif + +template<class T, class E, E max> +struct earray +{ + // no ctor/dtor and one public member variable for easy initialization + T _data[size_t(max)]; + + T& operator[](E v) + { + return _data[size_t(v)]; + } + + const T& operator[](E v) const + { + return _data[size_t(v)]; + } + + T *begin() + { + return _data; + } + + T *end() + { + return _data + size_t(max); + } +}; + +template<class T, class E> +class eptr +{ + T *_data; +public: + eptr(decltype(nullptr)=nullptr) + : _data(nullptr) + {} + + template<E max> + eptr(earray<T, E, max>& arr) + : _data(arr._data) + {} + + T& operator [](E v) + { + return _data[size_t(v)]; + } + + explicit operator bool() + { + return _data; + } + + bool operator not() + { + return not _data; + } +}; + +template<class It> +class IteratorPair +{ + It _b, _e; +public: + IteratorPair(It b, It e) + : _b(b), _e(e) + {} + + It begin() { return _b; } + It end() { return _e; } +}; + +template<class It> +IteratorPair<It> iterator_pair(It b, It e) +{ + return {b, e}; +} + +// std::underlying_type isn't supported until gcc 4.7 +// this is a poor man's emulation +template<class E> +struct underlying_type +{ + static_assert(std::is_enum<E>::value, "Only enums have underlying type!"); + typedef typename std::conditional< + std::is_signed<E>::value, + typename std::make_signed<E>::type, + typename std::make_unsigned<E>::type + >::type type; +}; + +template<class E, bool=std::is_enum<E>::value> +struct remove_enum +{ + typedef E type; +}; +template<class E> +struct remove_enum<E, true> +{ + typedef typename underlying_type<E>::type type; +}; + + +#define ENUM_BITWISE_OPERATORS(E) \ +inline \ +E operator & (E l, E r) \ +{ \ + typedef underlying_type<E>::type U; \ + return E(U(l) & U(r)); \ +} \ +inline \ +E operator | (E l, E r) \ +{ \ + typedef underlying_type<E>::type U; \ + return E(U(l) | U(r)); \ +} \ +inline \ +E operator ^ (E l, E r) \ +{ \ + typedef underlying_type<E>::type U; \ + return E(U(l) ^ U(r)); \ +} \ +inline \ +E& operator &= (E& l, E r) \ +{ \ + return l = l & r; \ +} \ +inline \ +E& operator |= (E& l, E r) \ +{ \ + return l = l | r; \ +} \ +inline \ +E& operator ^= (E& l, E r) \ +{ \ + return l = l ^ r; \ +} \ +inline \ +E operator ~ (E r) \ +{ \ + return E(-1) ^ r; \ +} + +template<class E> +class EnumValueIterator +{ + typedef typename underlying_type<E>::type U; + E value; +public: + EnumValueIterator(E v) + : value(v) + {} + + E operator *() + { + return value; + } + EnumValueIterator& operator++ () + { + value = E(U(value) + 1); + return *this; + } + EnumValueIterator& operator-- () + { + value = E(U(value) - 1); + return *this; + } + friend bool operator == (EnumValueIterator l, EnumValueIterator r) + { + return l.value == r.value; + } + friend bool operator != (EnumValueIterator l, EnumValueIterator r) + { + return !(l == r); + } +}; + +template<class E> +IteratorPair<EnumValueIterator<E>> erange(E b, E e) +{ + return {b, e}; +} + +namespace ph = std::placeholders; + +template<class A, class B> +typename std::common_type<A, B>::type min(A a, B b) +{ + return a < b ? a : b; +} + +template<class A, class B> +typename std::common_type<A, B>::type max(A a, B b) +{ + return b < a ? a : b; +} + +template<class T> +struct is_array_of_unknown_bound +: std::is_same<T, typename std::remove_extent<T>::type[]> +{}; + +template<class T, class... A> +typename std::enable_if<!is_array_of_unknown_bound<T>::value, std::unique_ptr<T>>::type make_unique(A&&... a) +{ + return std::unique_ptr<T>(new T(a...)); +} + +template<class T> +typename std::enable_if<is_array_of_unknown_bound<T>::value, std::unique_ptr<T>>::type make_unique(size_t sz) +{ + typedef typename std::remove_extent<T>::type E; + return std::unique_ptr<E[]>(new E[sz]); +} + +#endif // UTILS2_HPP |