]> code.bitgloo.com Git - clyne/entities.git/commitdiff
initial commit
authorClyne Sullivan <tullivan99@gmail.com>
Sun, 26 Feb 2017 20:42:46 +0000 (15:42 -0500)
committerClyne Sullivan <tullivan99@gmail.com>
Sun, 26 Feb 2017 20:42:46 +0000 (15:42 -0500)
Makefile [new file with mode: 0644]
bench.cpp [new file with mode: 0644]
benchpress.hpp [new file with mode: 0644]
cxxopts.hpp [new file with mode: 0644]
entities.hpp [new file with mode: 0644]
entitiesBenchmark.h [new file with mode: 0644]
main.cpp [new file with mode: 0644]
pvector.hpp [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..12b3c0c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,6 @@
+all:
+       g++ -std=c++17 -ggdb -pedantic -Wall -Wextra main.cpp -o main -lentityx
+
+test:
+       g++ -std=c++17 -Wall -Wextra bench.cpp -o test -O1
+       
diff --git a/bench.cpp b/bench.cpp
new file mode 100644 (file)
index 0000000..99824ac
--- /dev/null
+++ b/bench.cpp
@@ -0,0 +1,173 @@
+#include <string>
+#include <vector>
+#include <thread>
+#include <memory>
+
+#define BENCHPRESS_CONFIG_MAIN
+#include "benchpress.hpp"
+
+#include "entities.hpp"
+
+#include "entitiesBenchmark.h"
+
+inline void init_entities(EntityManager& entities, size_t nentities){
+    for (size_t i = 0; i < nentities; i++) {
+               auto entity = entities.create();
+
+               entity.assign<EntitiesBenchmark::PositionComponent>();
+               entity.assign<EntitiesBenchmark::VelocityComponent>();
+
+               if (i % 2) {
+                       entity.assign<EntitiesBenchmark::ComflabulationComponent>();
+               }
+       }
+}
+
+inline void runEntitiesSystemsEntityXBenchmark(benchpress::context* ctx, size_t nentities) {
+    EntitiesBenchmark::Application app;
+    auto& entities = app.em;
+
+    init_entities(entities, nentities);
+
+    ctx->reset_timer();
+    for (size_t i = 0; i < ctx->num_iterations(); ++i) {
+        app.update(EntitiesBenchmark::fakeDeltaTime);
+    }
+}
+
+
+
+
+BENCHMARK("entityx create destroy entity with components", [](benchpress::context* ctx) {
+    EntityManager entities;
+
+    ctx->reset_timer();
+    for (size_t i = 0; i < ctx->num_iterations(); ++i) {
+        auto entity = entities.create();
+
+        entity.assign<EntitiesBenchmark::PositionComponent>();
+               entity.assign<EntitiesBenchmark::VelocityComponent>();
+               entity.assign<EntitiesBenchmark::ComflabulationComponent>();
+
+        entities.kill(entity);
+    }
+})
+
+
+
+
+
+class BenchmarksEntityX {
+    public:
+    static const std::vector<int> ENTITIES;
+
+    static inline void makeBenchmarks(std::string name) {
+        makeBenchmarks(name, ENTITIES);
+    }
+    
+    static void makeBenchmarks(std::string name, const std::vector<int>& entities) {
+        for(int nentities : entities) {
+            std::string tag = "[" + std::to_string(nentities) + "]";
+
+            std::stringstream ss;
+            ss << std::right << std::setw(10) << tag << ' ';
+            ss << name << ' ';
+            ss << std::right << std::setw(8) << nentities;
+            ss << " entities component systems update";
+
+            std::string benchmark_name = ss.str();
+            BENCHMARK(benchmark_name, [nentities](benchpress::context* ctx) {
+                runEntitiesSystemsEntityXBenchmark(ctx, nentities);
+            })
+        }
+    }
+
+    BenchmarksEntityX(std::string name){
+        makeBenchmarks(name);
+    }
+};
+const std::vector<int> BenchmarksEntityX::ENTITIES = {
+    25, 50, 
+    100, 200, 400, 800, 
+    1600, 3200, 5000, 
+    10'000, 30'000, 
+    100'000, 500'000, 
+    1'000'000, 2'000'000
+};
+
+BenchmarksEntityX entityxbenchmarks ("entityx");
+
+
+
+
+
+/*
+BENCHMARK("[25]     entityx      25 entities component systems update", [](benchpress::context* ctx) {
+    runEntitiesSystemsEntityXBenchmark(ctx, 25);
+})
+
+BENCHMARK("[50]     entityx      50 entities component systems update", [](benchpress::context* ctx) {
+    runEntitiesSystemsEntityXBenchmark(ctx, 50);
+})
+
+BENCHMARK("[100]    entityx     100 entities component systems update", [](benchpress::context* ctx) {
+    runEntitiesSystemsEntityXBenchmark(ctx, 100);
+})
+
+BENCHMARK("[200]    entityx     200 entities component systems update", [](benchpress::context* ctx) {
+    runEntitiesSystemsEntityXBenchmark(ctx, 200);
+})
+
+
+BENCHMARK("[400]    entityx     400 entities component systems update", [](benchpress::context* ctx) {
+    runEntitiesSystemsEntityXBenchmark(ctx, 400);
+})
+
+
+BENCHMARK("[800]    entityx     800 entities component systems update", [](benchpress::context* ctx) {
+    runEntitiesSystemsEntityXBenchmark(ctx, 800);
+})
+
+
+BENCHMARK("[1600]    entityx   1600 entities component systems update", [](benchpress::context* ctx) {
+    runEntitiesSystemsEntityXBenchmark(ctx, 1600);
+})
+
+
+
+BENCHMARK("[3200]    entityx   3200 entities component systems update", [](benchpress::context* ctx) {
+    runEntitiesSystemsEntityXBenchmark(ctx, 3200);
+})
+
+
+BENCHMARK("[5000]    entityx   5000 entities component systems update", [](benchpress::context* ctx) {
+    runEntitiesSystemsEntityXBenchmark(ctx, 5000);
+})
+
+
+BENCHMARK("[10000]   entityx  10000 entities component systems update", [](benchpress::context* ctx) {
+    runEntitiesSystemsEntityXBenchmark(ctx, 10'000);
+})
+
+BENCHMARK("[30000]   entityx  30000 entities component systems update", [](benchpress::context* ctx) {
+    runEntitiesSystemsEntityXBenchmark(ctx, 30'000);
+})
+
+
+BENCHMARK("[100000]  entityx 100000 entities component systems update", [](benchpress::context* ctx) {
+    runEntitiesSystemsEntityXBenchmark(ctx, 100'000L);
+})
+
+
+BENCHMARK("[500000]  entityx 500000 entities component systems update", [](benchpress::context* ctx) {
+    runEntitiesSystemsEntityXBenchmark(ctx, 500'000L);
+})
+
+BENCHMARK("[1000000] entityx     1M entities component systems update", [](benchpress::context* ctx) {
+    runEntitiesSystemsEntityXBenchmark(ctx, 1'000'000L);
+})
+
+BENCHMARK("[2000000] entityx     2M entities component systems update", [](benchpress::context* ctx) {
+    runEntitiesSystemsEntityXBenchmark(ctx, 2'000'000L);
+})
+*/
diff --git a/benchpress.hpp b/benchpress.hpp
new file mode 100644 (file)
index 0000000..cb1bff4
--- /dev/null
@@ -0,0 +1,450 @@
+/*
+* Copyright (C) 2015 Christopher Gilbert.
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+#ifndef BENCHPRESS_HPP
+#define BENCHPRESS_HPP
+
+#include <algorithm>   // max, min
+#include <atomic>      // atomic_intmax_t
+#include <chrono>      // high_resolution_timer, duration
+#include <functional>  // function
+#include <iomanip>     // setw
+#include <iostream>    // cout
+#include <regex>       // regex, regex_match
+#include <sstream>     // stringstream
+#include <string>      // string
+#include <thread>      // thread
+#include <vector>      // vector
+
+namespace benchpress {
+
+/*
+ * The options class encapsulates all options for running benchmarks.
+ *
+ * When including benchpress, a main function can be emitted which includes a command-line parser for building an
+ * options object. However from time-to-time it may be necessary for the developer to have to build their own main
+ * stub and construct the options object manually.
+ *
+ * options opts;
+ * opts
+ *     .bench(".*")
+ *     .benchtime(1)
+ *     .cpu(4);
+ */
+class options {
+    std::string d_bench;
+    size_t      d_benchtime;
+    size_t      d_cpu;
+public:
+    options()
+        : d_bench(".*")
+        , d_benchtime(1)
+        , d_cpu(std::thread::hardware_concurrency())
+    {}
+    options& bench(const std::string& bench) {
+        d_bench = bench;
+        return *this;
+    }
+    options& benchtime(size_t benchtime) {
+        d_benchtime = benchtime;
+        return *this;
+    }
+    options& cpu(size_t cpu) {
+        d_cpu = cpu;
+        return *this;
+    }
+    std::string get_bench() const {
+        return d_bench;
+    }
+    size_t get_benchtime() const {
+        return d_benchtime;
+    }
+    size_t get_cpu() const {
+        return d_cpu;
+    }
+};
+
+class context;
+
+/*
+ * The benchmark_info class is used to store a function name / pointer pair.
+ *
+ * benchmark_info bi("example", [](benchpress::context* b) {
+ *     // benchmark function
+ * });
+ */
+class benchmark_info {
+    std::string                   d_name;
+    std::function<void(context*)> d_func;
+
+public:
+    benchmark_info(std::string name, std::function<void(context*)> func)
+        : d_name(name)
+        , d_func(func)
+    {}
+
+    std::string                   get_name() const { return d_name; }
+    std::function<void(context*)> get_func() const { return d_func; }
+};
+
+/*
+ * The registration class is responsible for providing a single global point of reference for registering
+ * benchmark functions.
+ *
+ * registration::get_ptr()->register_benchmark(info);
+ */
+class registration {
+    static registration*        d_this;
+    std::vector<benchmark_info> d_benchmarks;
+
+public:
+    static registration* get_ptr() {
+        if (nullptr == d_this) {
+            d_this = new registration();
+        }
+        return d_this;
+    }
+
+    void register_benchmark(benchmark_info& info) {
+        d_benchmarks.push_back(info);
+    }
+
+    std::vector<benchmark_info> get_benchmarks() { return d_benchmarks; }
+};
+
+/*
+ * The auto_register class is a helper used to register benchmarks.
+ */
+class auto_register {
+public:
+    auto_register(const std::string& name, std::function<void(context*)> func) {
+        benchmark_info info(name, func);
+        registration::get_ptr()->register_benchmark(info);
+    }
+};
+
+#define CONCAT(x, y) x ## y
+#define CONCAT2(x, y) CONCAT(x, y)
+
+// The BENCHMARK macro is a helper for creating benchmark functions and automatically registering them with the
+// registration class.
+#define BENCHMARK(x, f) benchpress::auto_register CONCAT2(register_, __LINE__)((x), (f));
+
+/*
+ * This function can be used to keep variables on the stack that would normally be optimised away
+ * by the compiler, without introducing any additional instructions or changing the behaviour of
+ * the program.
+ * 
+ * This function uses the Extended Asm syntax of GCC. The volatile keyword indicates that the 
+ * following instructions have some unknowable side-effect, and ensures that the code will neither 
+ * be moved, nor optimised away.
+ *
+ * AssemblerTemplate: No operands.
+ *
+ * OutputOperands: None.
+ *
+ * InputOperands: The "g" is a wildcard constraint which tells the compiler that it may choose what 
+ * to use for p (eg. a register OR a memory reference).
+ *
+ * Clobbers: The "memory" clobber tells the compiler that the assembly code performs reads or writes
+ * to the memory pointed to by one of the input parameters.
+ *
+ * Example usage:
+ *  std::vector<int> v;
+ *  v.reserve(10);
+ *  escape(v.data());
+ */
+void escape(void *p) {
+    asm volatile("" : : "g"(p) : "memory");
+}
+
+/*
+ * This function can be used to disable the optimiser. It has the effect of creating a read / write
+ * memory barrier for the compiler, meaning it does not assume that any values read from memory before
+ * the asm remain unchanged after that asm; it reloads them as needed.
+ *
+ * Example usage:
+ *  std::vector<int> v;
+ *  v.reserve(10);
+ *  escape(v.data());
+ *  v.push_back(42);
+ *  clobber(); // Ensure the integer pushed is read
+ */
+void clobber() {
+    asm volatile("" : : : "memory");
+}
+
+/*
+ * The result class is responsible for producing a printable string representation of a benchmark run.
+ */
+class result {
+    size_t                   d_num_iterations;
+    std::chrono::nanoseconds d_duration;
+    size_t                   d_num_bytes;
+
+public:
+    result(size_t num_iterations, std::chrono::nanoseconds duration, size_t num_bytes)
+        : d_num_iterations(num_iterations)
+        , d_duration(duration)
+        , d_num_bytes(num_bytes)
+    {}
+
+    size_t get_ns_per_op() const {
+        if (d_num_iterations <= 0) {
+            return 0;
+        }
+        return d_duration.count() / d_num_iterations;
+    }
+
+    double get_mb_per_s() const {
+        if (d_num_iterations <= 0 || d_duration.count() <= 0 || d_num_bytes <= 0) {
+            return 0;
+        }
+        return ((double(d_num_bytes) * double(d_num_iterations) / double(1e6)) /
+                double(std::chrono::duration_cast<std::chrono::seconds>(d_duration).count()));
+    }
+
+    std::string to_string() const {
+        std::stringstream tmp;
+        tmp << std::setw(12) << std::right << d_num_iterations;
+        size_t npo = get_ns_per_op();
+        tmp << std::setw(12) << std::right << npo << std::setw(0) << " ns/op";
+        double mbs = get_mb_per_s();
+        if (mbs > 0.0) {
+            tmp << std::setw(12) << std::right << mbs << std::setw(0) << " MB/s";
+        }
+        return std::string(tmp.str());
+    }
+};
+
+/*
+ * The parallel_context class is responsible for providing a thread-safe context for parallel benchmark code.
+ */
+class parallel_context {
+    std::atomic_intmax_t d_num_iterations;
+public:
+    parallel_context(size_t num_iterations)
+        : d_num_iterations(num_iterations)
+    {}
+
+    bool next() {
+        return (d_num_iterations.fetch_sub(1) > 0);
+    }
+};
+
+/*
+ * The context class is responsible for providing an interface for capturing benchmark metrics to benchmark functions.
+ */
+class context {
+    bool                                           d_timer_on;
+    std::chrono::high_resolution_clock::time_point d_start;
+    std::chrono::nanoseconds                       d_duration;
+    std::chrono::seconds                           d_benchtime;
+    size_t                                         d_num_iterations;
+    size_t                                         d_num_threads;
+    size_t                                         d_num_bytes;
+    benchmark_info                                 d_benchmark;
+
+public:
+    context(const benchmark_info& info, const options& opts)
+        : d_timer_on(false)
+        , d_start()
+        , d_duration()
+        , d_benchtime(std::chrono::seconds(opts.get_benchtime()))
+        , d_num_iterations(1)
+        , d_num_threads(opts.get_cpu())
+        , d_num_bytes(0)
+        , d_benchmark(info)
+    {}
+
+    size_t num_iterations() const { return d_num_iterations; }
+
+    void set_num_threads(size_t n) { d_num_threads = n; }
+    size_t num_threads() const { return d_num_threads; }
+
+    void start_timer() {
+        if (!d_timer_on) {
+            d_start = std::chrono::high_resolution_clock::now();
+            d_timer_on = true;
+        }
+    }
+    void stop_timer() {
+        if (d_timer_on) {
+            d_duration += std::chrono::high_resolution_clock::now() - d_start;
+            d_timer_on = false;
+        }
+    }
+    void reset_timer() {
+        if (d_timer_on) {
+            d_start = std::chrono::high_resolution_clock::now();
+        }
+        d_duration = std::chrono::nanoseconds::zero();
+    }
+
+    void set_bytes(int64_t bytes) { d_num_bytes = bytes; }
+
+    size_t get_ns_per_op() {
+        if (d_num_iterations <= 0) {
+            return 0;
+        }
+        return d_duration.count() / d_num_iterations;
+    }
+
+    void run_n(size_t n) {
+        d_num_iterations = n;
+        reset_timer();
+        start_timer();
+        d_benchmark.get_func()(this);
+        stop_timer();
+    }
+
+    void run_parallel(std::function<void(parallel_context*)> f) {
+        parallel_context pc(d_num_iterations);
+        std::vector<std::thread> threads;
+        for (size_t i = 0; i < d_num_threads; ++i) {
+            threads.push_back(std::thread([&pc,&f]() -> void {
+                f(&pc);
+            }));
+        }
+        for(auto& thread : threads){
+            thread.join();
+        }
+    }
+
+    result run() {
+        size_t n = 1;
+        run_n(n);
+        while (d_duration < d_benchtime && n < 1e9) {
+            size_t last = n;
+            if (get_ns_per_op() == 0) {
+                n = 1e9;
+            } else {
+                n = d_duration.count() / get_ns_per_op();
+            }
+            n = std::max(std::min(n+n/2, 100*last), last+1);
+            n = round_up(n);
+            run_n(n);
+        }
+        return result(n, d_duration, d_num_bytes);
+    }
+
+private:
+    template<typename T>
+    T round_down_10(T n) {
+        int tens = 0;
+        while (n > 10) {
+            n /= 10;
+            tens++;
+        }
+        int result = 1;
+        for (int i = 0; i < tens; ++i) {
+            result *= 10;
+        }
+        return result;
+    }
+
+    template<typename T>
+    T round_up(T n) {
+        T base = round_down_10(n);
+        if (n < (2 * base)) {
+            return 2 * base;
+        }
+        if (n < (5 * base)) {
+            return 5 * base;
+        }
+        return 10 * base;
+    }
+};
+
+/*
+ * The run_benchmarks function will run the registered benchmarks.
+ */
+void run_benchmarks(const options& opts) {
+    std::regex match_r(opts.get_bench());
+    auto benchmarks = registration::get_ptr()->get_benchmarks();
+    for (auto& info : benchmarks) {
+        if (std::regex_match(info.get_name(), match_r)) {
+            context c(info, opts);
+            auto r = c.run();
+            std::cout << std::setw(35) << std::left << info.get_name() << r.to_string() << std::endl;
+        }
+    }
+}
+
+} // namespace benchpress
+
+/*
+ * If BENCHPRESS_CONFIG_MAIN is defined when the file is included then a main function will be emitted which provides a
+ * command-line parser and then executes run_benchmarks.
+ */
+#ifdef BENCHPRESS_CONFIG_MAIN
+#include "cxxopts.hpp"
+benchpress::registration* benchpress::registration::d_this;
+int main(int argc, char** argv) {
+    std::chrono::high_resolution_clock::time_point bp_start = std::chrono::high_resolution_clock::now();
+    benchpress::options bench_opts;
+    try {
+        cxxopts::Options cmd_opts(argv[0], " - command line options");
+        cmd_opts.add_options()
+            ("bench", "run benchmarks matching the regular expression", cxxopts::value<std::string>()
+                ->default_value(".*"))
+            ("benchtime", "run enough iterations of each benchmark to take t seconds", cxxopts::value<size_t>()
+                ->default_value("1"))
+            ("cpu", "specify the number of threads to use for parallel benchmarks", cxxopts::value<size_t>()
+                ->default_value(std::to_string(std::thread::hardware_concurrency())))
+            ("list", "list all available benchmarks")
+            ("help", "print help")
+        ;
+        cmd_opts.parse(argc, argv);
+        if (cmd_opts.count("help")) {
+            std::cout << cmd_opts.help({""}) << std::endl;
+            exit(0);
+        }
+        if (cmd_opts.count("bench")) {
+            bench_opts.bench(cmd_opts["bench"].as<std::string>());
+        }
+        if (cmd_opts.count("benchtime")) {
+            bench_opts.benchtime(cmd_opts["benchtime"].as<size_t>());
+        }
+        if (cmd_opts.count("cpu")) {
+            bench_opts.cpu(cmd_opts["cpu"].as<size_t>());
+        }
+        if (cmd_opts.count("list")) {
+            auto benchmarks = benchpress::registration::get_ptr()->get_benchmarks();
+            for (auto& info : benchmarks) {
+                std::cout << info.get_name() << std::endl;
+            }
+            exit(EXIT_SUCCESS);
+        }
+    } catch (const cxxopts::OptionException& e) {
+        std::cout << "error parsing options: " << e.what() << std::endl;
+        exit(1);
+    }
+    benchpress::run_benchmarks(bench_opts);
+    float duration = std::chrono::duration_cast<std::chrono::milliseconds>(
+            std::chrono::high_resolution_clock::now() - bp_start
+    ).count() / 1000.f;
+    std::cout << argv[0] << " " << duration << "s" << std::endl;
+    return 0;
+}
+#endif
+
+#endif // BENCHPRESS_HPP
diff --git a/cxxopts.hpp b/cxxopts.hpp
new file mode 100644 (file)
index 0000000..047190e
--- /dev/null
@@ -0,0 +1,1312 @@
+/*
+
+Copyright (c) 2014 Jarryd Beck
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+#ifndef CXX_OPTS_HPP
+#define CXX_OPTS_HPP
+
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
+#endif
+
+#include <exception>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <regex>
+#include <sstream>
+#include <string>
+#include <vector>
+
+//when we ask cxxopts to use Unicode, help strings are processed using ICU,
+//which results in the correct lengths being computed for strings when they
+//are formatted for the help output
+//it is necessary to make sure that <unicode/unistr.h> can be found by the
+//compiler, and that icu-uc is linked in to the binary.
+
+#ifdef CXXOPTS_USE_UNICODE
+#include <unicode/unistr.h>
+
+namespace cxxopts
+{
+  typedef icu::UnicodeString String;
+
+  inline
+  String
+  toLocalString(std::string s)
+  {
+    return icu::UnicodeString::fromUTF8(s);
+  }
+
+  class UnicodeStringIterator : public
+    std::iterator<std::forward_iterator_tag, int32_t>
+  {
+    public:
+
+    UnicodeStringIterator(const icu::UnicodeString* s, int32_t pos)
+    : s(s)
+    , i(pos)
+    {
+    }
+
+    value_type
+    operator*() const
+    {
+      return s->char32At(i);
+    }
+
+    bool
+    operator==(const UnicodeStringIterator& rhs) const
+    {
+      return s == rhs.s && i == rhs.i;
+    }
+
+    bool
+    operator!=(const UnicodeStringIterator& rhs) const
+    {
+      return !(*this == rhs);
+    }
+
+    UnicodeStringIterator&
+    operator++()
+    {
+      ++i;
+      return *this;
+    }
+
+    UnicodeStringIterator
+    operator+(int32_t v)
+    {
+      return UnicodeStringIterator(s, i + v);
+    }
+
+    private:
+    const icu::UnicodeString* s;
+    int32_t i;
+  };
+
+  inline
+  String&
+  stringAppend(String&s, String a)
+  {
+    return s.append(std::move(a));
+  }
+
+  inline
+  String&
+  stringAppend(String& s, int n, UChar32 c)
+  {
+    for (int i = 0; i != n; ++i)
+    {
+      s.append(c);
+    }
+
+    return s;
+  }
+
+  template <typename Iterator>
+  String&
+  stringAppend(String& s, Iterator begin, Iterator end)
+  {
+    while (begin != end)
+    {
+      s.append(*begin);
+      ++begin;
+    }
+
+    return s;
+  }
+
+  inline
+  size_t
+  stringLength(const String& s)
+  {
+    return s.length();
+  }
+
+  inline
+  std::string
+  toUTF8String(const String& s)
+  {
+    std::string result;
+    s.toUTF8String(result);
+
+    return result;
+  }
+}
+
+namespace std
+{
+  cxxopts::UnicodeStringIterator
+  begin(const icu::UnicodeString& s)
+  {
+    return cxxopts::UnicodeStringIterator(&s, 0);
+  }
+
+  cxxopts::UnicodeStringIterator
+  end(const icu::UnicodeString& s)
+  {
+    return cxxopts::UnicodeStringIterator(&s, s.length());
+  }
+}
+
+//ifdef CXXOPTS_USE_UNICODE
+#else
+
+namespace cxxopts
+{
+  typedef std::string String;
+
+  template <typename T>
+  T
+  toLocalString(T&& t)
+  {
+    return t;
+  }
+
+  inline
+  size_t
+  stringLength(const String& s)
+  {
+    return s.length();
+  }
+
+  inline
+  String&
+  stringAppend(String&s, String a)
+  {
+    return s.append(std::move(a));
+  }
+
+  inline
+  String&
+  stringAppend(String& s, size_t n, char c)
+  {
+    return s.append(n, c);
+  }
+
+  template <typename Iterator>
+  String&
+  stringAppend(String& s, Iterator begin, Iterator end)
+  {
+    return s.append(begin, end);
+  }
+
+  template <typename T>
+  std::string
+  toUTF8String(T&& t)
+  {
+    return std::forward<T>(t);
+  }
+
+}
+
+//ifdef CXXOPTS_USE_UNICODE
+#endif
+
+namespace cxxopts
+{
+  class Value : public std::enable_shared_from_this<Value>
+  {
+    public:
+
+    virtual void
+    parse(const std::string& text) const = 0;
+
+    virtual void
+    parse() const = 0;
+
+    virtual bool
+    has_arg() const = 0;
+
+    virtual bool
+    has_default() const = 0;
+
+    virtual bool
+    has_implicit() const = 0;
+
+    virtual std::string
+    get_default_value() const = 0;
+
+    virtual std::string
+    get_implicit_value() const = 0;
+
+    virtual std::shared_ptr<Value>
+    default_value(const std::string& value) = 0;
+
+    virtual std::shared_ptr<Value>
+    implicit_value(const std::string& value) = 0;
+  };
+
+  class OptionException : public std::exception
+  {
+    public:
+    OptionException(const std::string& message)
+    : m_message(message)
+    {
+    }
+
+    virtual const char*
+    what() const noexcept
+    {
+      return m_message.c_str();
+    }
+
+    private:
+    std::string m_message;
+  };
+
+  class OptionSpecException : public OptionException
+  {
+    public:
+
+    OptionSpecException(const std::string& message)
+    : OptionException(message)
+    {
+    }
+  };
+
+  class OptionParseException : public OptionException
+  {
+    public:
+    OptionParseException(const std::string& message)
+    : OptionException(message)
+    {
+    }
+  };
+
+  class option_exists_error : public OptionSpecException
+  {
+    public:
+    option_exists_error(const std::string& option)
+    : OptionSpecException(u8"Option ?" + option + u8"? already exists")
+    {
+    }
+  };
+
+  class invalid_option_format_error : public OptionSpecException
+  {
+    public:
+    invalid_option_format_error(const std::string& format)
+    : OptionSpecException(u8"Invalid option format ?" + format + u8"?")
+    {
+    }
+  };
+
+  class option_not_exists_exception : public OptionParseException
+  {
+    public:
+    option_not_exists_exception(const std::string& option)
+    : OptionParseException(u8"Option ?" + option + u8"? does not exist")
+    {
+    }
+  };
+
+  class missing_argument_exception : public OptionParseException
+  {
+    public:
+    missing_argument_exception(const std::string& option)
+    : OptionParseException(u8"Option ?" + option + u8"? is missing an argument")
+    {
+    }
+  };
+
+  class option_requires_argument_exception : public OptionParseException
+  {
+    public:
+    option_requires_argument_exception(const std::string& option)
+    : OptionParseException(u8"Option ?" + option + u8"? requires an argument")
+    {
+    }
+  };
+
+  class option_not_has_argument_exception : public OptionParseException
+  {
+    public:
+    option_not_has_argument_exception
+    (
+      const std::string& option,
+      const std::string& arg
+    )
+    : OptionParseException(
+        u8"Option ?" + option + u8"? does not take an argument, but argument?"
+        + arg + "? given")
+    {
+    }
+  };
+
+  class option_not_present_exception : public OptionParseException
+  {
+    public:
+    option_not_present_exception(const std::string& option)
+    : OptionParseException(u8"Option ?" + option + u8"? not present")
+    {
+    }
+  };
+
+  class argument_incorrect_type : public OptionParseException
+  {
+    public:
+    argument_incorrect_type
+    (
+      const std::string& arg
+    )
+    : OptionParseException(
+      u8"Argument ?" + arg + u8"? failed to parse"
+    )
+    {
+    }
+  };
+
+  namespace values
+  {
+    template <typename T>
+    void
+    parse_value(const std::string& text, T& value)
+    {
+      std::istringstream is(text);
+      if (!(is >> value))
+      {
+        std::cerr << "cannot parse empty value" << std::endl;
+        throw argument_incorrect_type(text);
+      }
+
+      if (is.rdbuf()->in_avail() != 0)
+      {
+        throw argument_incorrect_type(text);
+      }
+    }
+
+    template <typename T>
+    void
+    parse_value(const std::string& text, std::vector<T>& value)
+    {
+      T v;
+      parse_value(text, v);
+      value.push_back(v);
+    }
+
+    inline
+    void
+    parse_value(const std::string& /*text*/, bool& value)
+    {
+      //TODO recognise on, off, yes, no, enable, disable
+      //so that we can write --long=yes explicitly
+      value = true;
+    }
+
+    inline
+    void
+    parse_value(const std::string& text, std::string& value)
+    {
+      value = text;
+    }
+
+    template <typename T>
+    struct value_has_arg
+    {
+      static constexpr bool value = true;
+    };
+
+    template <>
+    struct value_has_arg<bool>
+    {
+      static constexpr bool value = false;
+    };
+
+    template <typename T>
+    class standard_value : public Value
+    {
+      public:
+      standard_value()
+      : m_result(std::make_shared<T>())
+      , m_store(m_result.get())
+      {
+      }
+
+      standard_value(T* t)
+      : m_store(t)
+      {
+      }
+
+      void
+      parse(const std::string& text) const
+      {
+        if (m_implicit && text.empty())
+        {
+          parse_value(m_implicit_value, *m_store);
+        }
+        else
+        {
+          parse_value(text, *m_store);
+        }
+      }
+
+      void
+      parse() const
+      {
+        parse_value(m_default_value, *m_store);
+      }
+
+      bool
+      has_arg() const
+      {
+        return value_has_arg<T>::value;
+      }
+
+      bool
+      has_default() const
+      {
+        return m_default;
+      }
+
+      bool
+      has_implicit() const
+      {
+        return m_implicit;
+      }
+
+      virtual std::shared_ptr<Value>
+      default_value(const std::string& value){
+        m_default = true;
+        m_default_value = value;
+        return shared_from_this();
+      }
+
+      virtual std::shared_ptr<Value>
+      implicit_value(const std::string& value){
+        m_implicit = true;
+        m_implicit_value = value;
+        return shared_from_this();
+      }
+
+      std::string
+      get_default_value() const
+      {
+        return m_default_value;
+      }
+
+      std::string
+      get_implicit_value() const
+      {
+        return m_implicit_value;
+      }
+
+      const T&
+      get() const
+      {
+        if (m_store == nullptr)
+        {
+          return *m_result;
+        }
+        else
+        {
+          return *m_store;
+        }
+      }
+
+      protected:
+      std::shared_ptr<T> m_result;
+      T* m_store;
+      bool m_default = false;
+      std::string m_default_value;
+      bool m_implicit = false;
+      std::string m_implicit_value;
+    };
+  }
+
+  template <typename T>
+  std::shared_ptr<Value>
+  value()
+  {
+    return std::make_shared<values::standard_value<T>>();
+  }
+
+  template <typename T>
+  std::shared_ptr<Value>
+  value(T& t)
+  {
+    return std::make_shared<values::standard_value<T>>(&t);
+  }
+
+  class OptionAdder;
+
+  class OptionDetails
+  {
+    public:
+    OptionDetails
+    (
+      const String& description,
+      std::shared_ptr<const Value> value
+    )
+    : m_desc(description)
+    , m_value(value)
+    , m_count(0)
+    {
+    }
+
+    const String&
+    description() const
+    {
+      return m_desc;
+    }
+
+    bool
+    has_arg() const
+    {
+      return m_value->has_arg();
+    }
+
+    void
+    parse(const std::string& text)
+    {
+      m_value->parse(text);
+      ++m_count;
+    }
+
+    void
+    parse_default()
+    {
+      m_value->parse();
+      ++m_count;
+    }
+
+    int
+    count() const
+    {
+      return m_count;
+    }
+
+    const Value& value() const {
+        return *m_value;
+    }
+
+    template <typename T>
+    const T&
+    as() const
+    {
+#ifdef CXXOPTS_NO_RTTI
+      return static_cast<const values::standard_value<T>&>(*m_value).get();
+#else
+      return dynamic_cast<const values::standard_value<T>&>(*m_value).get();
+#endif
+    }
+
+    private:
+    String m_desc;
+    std::shared_ptr<const Value> m_value;
+    int m_count;
+  };
+
+  struct HelpOptionDetails
+  {
+    std::string s;
+    std::string l;
+    String desc;
+    bool has_arg;
+    bool has_default;
+    std::string default_value;
+    bool has_implicit;
+    std::string implicit_value;
+    std::string arg_help;
+  };
+
+  struct HelpGroupDetails
+  {
+    std::string name;
+    std::string description;
+    std::vector<HelpOptionDetails> options;
+  };
+
+  class Options
+  {
+    public:
+
+    Options(std::string program, std::string help_string = "")
+    : m_program(std::move(program))
+    , m_help_string(toLocalString(std::move(help_string)))
+    {
+    }
+
+    inline
+    void
+    parse(int& argc, char**& argv);
+
+    inline
+    OptionAdder
+    add_options(std::string group = "");
+
+    inline
+    void
+    add_option
+    (
+      const std::string& group,
+      const std::string& s,
+      const std::string& l,
+      std::string desc,
+      std::shared_ptr<const Value> value,
+      std::string arg_help
+    );
+
+    int
+    count(const std::string& o) const
+    {
+      auto iter = m_options.find(o);
+      if (iter == m_options.end())
+      {
+        return 0;
+      }
+
+      return iter->second->count();
+    }
+
+    const OptionDetails&
+    operator[](const std::string& option) const
+    {
+      auto iter = m_options.find(option);
+
+      if (iter == m_options.end())
+      {
+        throw option_not_present_exception(option);
+      }
+
+      return *iter->second;
+    }
+
+    //parse positional arguments into the given option
+    inline
+    void
+    parse_positional(std::string option);
+
+    inline
+    std::string
+    help(const std::vector<std::string>& groups = {""}) const;
+
+    inline
+    const std::vector<std::string>
+    groups() const;
+
+    inline
+    const HelpGroupDetails&
+    group_help(const std::string& group) const;
+
+    private:
+
+    inline
+    void
+    add_one_option
+    (
+      const std::string& option,
+      std::shared_ptr<OptionDetails> details
+    );
+
+    inline
+    bool
+    consume_positional(std::string a);
+
+    inline
+    void
+    add_to_option(const std::string& option, const std::string& arg);
+
+    inline
+    void
+    parse_option
+    (
+      std::shared_ptr<OptionDetails> value,
+      const std::string& name,
+      const std::string& arg = ""
+    );
+
+    inline
+    void
+    checked_parse_arg
+    (
+      int argc,
+      char* argv[],
+      int& current,
+      std::shared_ptr<OptionDetails> value,
+      const std::string& name
+    );
+
+    inline
+    String
+    help_one_group(const std::string& group) const;
+
+    std::string m_program;
+    String m_help_string;
+
+    std::map<std::string, std::shared_ptr<OptionDetails>> m_options;
+    std::string m_positional;
+
+    //mapping from groups to help options
+    std::map<std::string, HelpGroupDetails> m_help;
+  };
+
+  class OptionAdder
+  {
+    public:
+
+    OptionAdder(Options& options, std::string group)
+    : m_options(options), m_group(std::move(group))
+    {
+    }
+
+    inline
+    OptionAdder&
+    operator()
+    (
+      const std::string& opts,
+      const std::string& desc,
+      std::shared_ptr<const Value> value
+        = ::cxxopts::value<bool>(),
+      std::string arg_help = ""
+    );
+
+    private:
+    Options& m_options;
+    std::string m_group;
+  };
+
+}
+
+namespace cxxopts
+{
+
+  namespace
+  {
+
+    constexpr int OPTION_LONGEST = 30;
+    constexpr int OPTION_DESC_GAP = 2;
+
+    std::basic_regex<char> option_matcher
+      ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([a-zA-Z]+)");
+
+    std::basic_regex<char> option_specifier
+      ("(([a-zA-Z]),)?([a-zA-Z0-9][-_a-zA-Z0-9]+)");
+
+    String
+    format_option
+    (
+      const HelpOptionDetails& o
+    )
+    {
+      auto& s = o.s;
+      auto& l = o.l;
+
+      String result = "  ";
+
+      if (s.size() > 0)
+      {
+        result += "-" + toLocalString(s) + ",";
+      }
+      else
+      {
+        result += "   ";
+      }
+
+      if (l.size() > 0)
+      {
+        result += " --" + toLocalString(l);
+      }
+
+      if (o.has_arg)
+      {
+        auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg";
+
+        if (o.has_implicit)
+        {
+          result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]";
+        }
+        else
+        {
+          result += " " + arg;
+        }
+      }
+
+      return result;
+    }
+
+    String
+    format_description
+    (
+      const HelpOptionDetails& o,
+      size_t start,
+      size_t width
+    )
+    {
+      auto desc = o.desc;
+
+      if (o.has_default)
+      {
+        desc += toLocalString(" (default:" + o.default_value + ")");
+      }
+
+      String result;
+
+      auto current = std::begin(desc);
+      auto startLine = current;
+      auto lastSpace = current;
+
+      auto size = size_t{};
+
+      while (current != std::end(desc))
+      {
+        if (*current == ' ')
+        {
+          lastSpace = current;
+        }
+
+        if (size > width)
+        {
+          if (lastSpace == startLine)
+          {
+            stringAppend(result, startLine, current + 1);
+            stringAppend(result, "\n");
+            stringAppend(result, start, ' ');
+            startLine = current + 1;
+            lastSpace = startLine;
+          }
+          else
+          {
+            stringAppend(result, startLine, lastSpace);
+            stringAppend(result, "\n");
+            stringAppend(result, start, ' ');
+            startLine = lastSpace + 1;
+          }
+          size = 0;
+        }
+        else
+        {
+          ++size;
+        }
+
+        ++current;
+      }
+
+      //append whatever is left
+      stringAppend(result, startLine, current);
+
+      return result;
+    }
+  }
+
+OptionAdder
+Options::add_options(std::string group)
+{
+  return OptionAdder(*this, std::move(group));
+}
+
+OptionAdder&
+OptionAdder::operator()
+(
+  const std::string& opts,
+  const std::string& desc,
+  std::shared_ptr<const Value> value,
+  std::string arg_help
+)
+{
+  std::match_results<const char*> result;
+  std::regex_match(opts.c_str(), result, option_specifier);
+
+  if (result.empty())
+  {
+    throw invalid_option_format_error(opts);
+  }
+
+  const auto& s = result[2];
+  const auto& l = result[3];
+
+  m_options.add_option(m_group, s.str(), l.str(), desc, value,
+    std::move(arg_help));
+
+  return *this;
+}
+
+void
+Options::parse_option
+(
+  std::shared_ptr<OptionDetails> value,
+  const std::string& /*name*/,
+  const std::string& arg
+)
+{
+  value->parse(arg);
+}
+
+void
+Options::checked_parse_arg
+(
+  int argc,
+  char* argv[],
+  int& current,
+  std::shared_ptr<OptionDetails> value,
+  const std::string& name
+)
+{
+  if (current + 1 >= argc)
+  {
+    if (value->value().has_implicit())
+    {
+      parse_option(value, name, "");
+    }
+    else
+    {
+      throw missing_argument_exception(name);
+    }
+  }
+  else
+  {
+    if (argv[current + 1][0] == '-' && value->value().has_implicit())
+    {
+      parse_option(value, name, "");
+    }
+    else
+    {
+      parse_option(value, name, argv[current + 1]);
+      ++current;
+    }
+  }
+}
+
+void
+Options::add_to_option(const std::string& option, const std::string& arg)
+{
+  auto iter = m_options.find(option);
+
+  if (iter == m_options.end())
+  {
+    throw option_not_exists_exception(option);
+  }
+
+  parse_option(iter->second, option, arg);
+}
+
+bool
+Options::consume_positional(std::string a)
+{
+  if (m_positional.size() > 0)
+  {
+    add_to_option(m_positional, a);
+    return true;
+  }
+  else
+  {
+    return false;
+  }
+}
+
+void
+Options::parse_positional(std::string option)
+{
+  m_positional = std::move(option);
+}
+
+void
+Options::parse(int& argc, char**& argv)
+{
+  int current = 1;
+
+  int nextKeep = 1;
+
+  while (current != argc)
+  {
+    std::match_results<const char*> result;
+    std::regex_match(argv[current], result, option_matcher);
+
+    if (result.empty())
+    {
+      //not a flag
+
+      //if true is returned here then it was consumed, otherwise it is
+      //ignored
+      if (consume_positional(argv[current]))
+      {
+      }
+      else
+      {
+        argv[nextKeep] = argv[current];
+        ++nextKeep;
+      }
+      //if we return from here then it was parsed successfully, so continue
+    }
+    else
+    {
+      //short or long option?
+      if (result[4].length() != 0)
+      {
+        const std::string& s = result[4];
+
+        for (std::size_t i = 0; i != s.size(); ++i)
+        {
+          std::string name(1, s[i]);
+          auto iter = m_options.find(name);
+
+          if (iter == m_options.end())
+          {
+            throw option_not_exists_exception(name);
+          }
+
+          auto value = iter->second;
+
+          //if no argument then just add it
+          if (!value->has_arg())
+          {
+            parse_option(value, name);
+          }
+          else
+          {
+            //it must be the last argument
+            if (i + 1 == s.size())
+            {
+              checked_parse_arg(argc, argv, current, value, name);
+            }
+            else if (value->value().has_implicit())
+            {
+              parse_option(value, name, "");
+            }
+            else
+            {
+              //error
+              throw option_requires_argument_exception(name);
+            }
+          }
+        }
+      }
+      else if (result[1].length() != 0)
+      {
+        const std::string& name = result[1];
+
+        auto iter = m_options.find(name);
+
+        if (iter == m_options.end())
+        {
+          throw option_not_exists_exception(name);
+        }
+
+        auto opt = iter->second;
+
+        //equals provided for long option?
+        if (result[3].length() != 0)
+        {
+          //parse the option given
+
+          //but if it doesn't take an argument, this is an error
+          if (!opt->has_arg())
+          {
+            throw option_not_has_argument_exception(name, result[3]);
+          }
+
+          parse_option(opt, name, result[3]);
+        }
+        else
+        {
+          if (opt->has_arg())
+          {
+            //parse the next argument
+            checked_parse_arg(argc, argv, current, opt, name);
+          }
+          else
+          {
+            //parse with empty argument
+            parse_option(opt, name);
+          }
+        }
+      }
+
+    }
+
+    ++current;
+  }
+
+  for (auto& opt : m_options)
+  {
+    auto& detail = opt.second;
+    auto& value = detail->value();
+
+    if(!detail->count() && value.has_default()){
+      detail->parse_default();
+    }
+  }
+
+  argc = nextKeep;
+}
+
+void
+Options::add_option
+(
+  const std::string& group,
+  const std::string& s,
+  const std::string& l,
+  std::string desc,
+  std::shared_ptr<const Value> value,
+  std::string arg_help
+)
+{
+  auto stringDesc = toLocalString(std::move(desc));
+  auto option = std::make_shared<OptionDetails>(stringDesc, value);
+
+  if (s.size() > 0)
+  {
+    add_one_option(s, option);
+  }
+
+  if (l.size() > 0)
+  {
+    add_one_option(l, option);
+  }
+
+  //add the help details
+  auto& options = m_help[group];
+
+  options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,
+      value->has_arg(),
+      value->has_default(), value->get_default_value(),
+      value->has_implicit(), value->get_implicit_value(),
+      std::move(arg_help)});
+}
+
+void
+Options::add_one_option
+(
+  const std::string& option,
+  std::shared_ptr<OptionDetails> details
+)
+{
+  auto in = m_options.emplace(option, details);
+
+  if (!in.second)
+  {
+    throw option_exists_error(option);
+  }
+}
+
+String
+Options::help_one_group(const std::string& g) const
+{
+  typedef std::vector<std::pair<String, String>> OptionHelp;
+
+  auto group = m_help.find(g);
+  if (group == m_help.end())
+  {
+    return "";
+  }
+
+  OptionHelp format;
+
+  size_t longest = 0;
+
+  String result;
+
+  if (!g.empty())
+  {
+    result += toLocalString(" " + g + " options:\n\n");
+  }
+
+  for (const auto& o : group->second.options)
+  {
+    auto s = format_option(o);
+    longest = std::max(longest, stringLength(s));
+    format.push_back(std::make_pair(s, String()));
+  }
+
+  longest = std::min(longest, static_cast<size_t>(OPTION_LONGEST));
+
+  //widest allowed description
+  auto allowed = size_t{76} - longest - OPTION_DESC_GAP;
+
+  auto fiter = format.begin();
+  for (const auto& o : group->second.options)
+  {
+    auto d = format_description(o, longest + OPTION_DESC_GAP, allowed);
+
+    result += fiter->first;
+    if (stringLength(fiter->first) > longest)
+    {
+      result += "\n";
+      result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' '));
+    }
+    else
+    {
+      result += toLocalString(std::string(longest + OPTION_DESC_GAP -
+        stringLength(fiter->first),
+        ' '));
+    }
+    result += d;
+    result += "\n";
+
+    ++fiter;
+  }
+
+  return result;
+}
+
+std::string
+Options::help(const std::vector<std::string>& groups) const
+{
+  String result = "Usage:\n  " + toLocalString(m_program) + " [OPTION...]"
+    + m_help_string + "\n\n";
+
+  for (std::size_t i = 0; i < groups.size(); ++i)
+  {
+    result += help_one_group(groups[i]);
+    if (i < groups.size() - 1)
+    {
+      result += "\n";
+    }
+  }
+
+  return toUTF8String(result);
+}
+
+const std::vector<std::string>
+Options::groups() const
+{
+  std::vector<std::string> g;
+
+  std::transform(
+    m_help.begin(),
+    m_help.end(),
+    std::back_inserter(g),
+    [] (const std::map<std::string, HelpGroupDetails>::value_type& pair)
+    {
+      return pair.first;
+    }
+  );
+
+  return g;
+}
+
+const HelpGroupDetails&
+Options::group_help(const std::string& group) const
+{
+  return m_help.at(group);
+}
+
+}
+
+#if defined(__GNU__)
+#pragma GCC diagnostic pop
+#endif
+
+#endif //CXX_OPTS_HPP
diff --git a/entities.hpp b/entities.hpp
new file mode 100644 (file)
index 0000000..3580d65
--- /dev/null
@@ -0,0 +1,255 @@
+/**
+ * @file entities.hpp
+ * @brief A simply-written entity component system.
+ */
+#ifndef ENTITIES_HPP_
+#define ENTITIES_HPP_
+
+#include <algorithm> // std::find_if
+#include <type_traits> // std::is_convertible
+//#include "pvector.hpp"
+#include <vector>
+
+#define Container std::vector
+
+/**
+ * @class Component
+ * A base class for all components to inherit.
+ */
+class Component {
+       /**
+        * Constructs the class using the given XML element(s).
+        */
+       virtual void fromXML(/*TODO*/) {}
+};
+
+/** An ID number for entities. */
+using Id = unsigned int;
+
+/**
+ * @struct EntityData
+ * Contains all components and an ID, to form an entity.
+ */
+struct EntityData {
+       /** The entity's ID. */
+       Id id;
+
+       /** A vector of all components. */
+       Container<Component*> components;
+
+       /** Constructs an entity with the given ID. */
+       EntityData(Id _id = -1)
+               : id (_id) {}
+
+       /** Compares two entities through their IDs. */
+       bool operator==(const EntityData& e) {
+               return id == e.id;
+       }
+};
+
+/**
+ * @struct Entity
+ * Allows access to an entity and it's components.
+ * Note that this is not the actual entity's data, that is stored in the
+ * EntityManager's EntityData array.
+ */
+struct Entity {
+       Container<EntityData>::iterator pos;
+
+       /** Constructs an entity object to handle the given entity data. */
+       Entity(Container<EntityData>::iterator p)
+               :  pos(p) {}
+       Entity(EntityData& d)
+               : pos(&d) {}
+
+       /**
+        * Assigns a component to the entity.
+        * @param args arguments to pass to the component's constructor.
+        * @return a pointer to the new component
+        */
+       template<class T, typename... Args>
+       T* assign(Args... args) {
+               static_assert(std::is_convertible<T*, Component*>::value,
+                       "components must inherit Component base class");
+               auto comp = new T(args...);
+               (*pos).components.push_back(comp);
+               return comp;
+       }
+
+       /**
+        * Removes a component of the given type from the entity.
+        */
+       template<class T>
+       void remove(void) {
+               static_assert(std::is_convertible<T*, Component*>::value,
+                       "components must inherit Component base class");
+               auto c = std::find_if((*pos).components.begin(), (*pos).components.end(),
+                       [](auto c){ return dynamic_cast<T*>(c); });
+               if (c != (*pos).components.end())
+                       (*pos).components.erase(c);
+       }
+
+       /**
+        * Tests if the entity has a component of the given type.
+        * @return true if the entity has the component
+        */
+       template<class T>
+       bool hasComponent(void) const {
+               static_assert(std::is_convertible<T*, Component*>::value,
+                       "components must inherit Component base class");
+               auto c = std::find_if((*pos).components.begin(), (*pos).components.end(),
+                       [](auto c){ return dynamic_cast<T*>(c); });
+               return c != (*pos).components.end();
+       }
+
+       /**
+        * Fetches a component from the entity.
+        * @return the component, nullptr if the entity does not have it
+        */
+       template<class T>
+       T* component(void) {
+               static_assert(std::is_convertible<T*, Component*>::value,
+                       "components must inherit Component base class");
+               auto c = std::find_if((*pos).components.begin(), (*pos).components.end(),
+                       [](auto c){ return dynamic_cast<T*>(c); });
+               return (c != (*pos).components.end()) ? dynamic_cast<T*>(*c) : nullptr;
+       }
+};
+
+/**
+ * @class EntityManager
+ * Manages a group of entities.
+ */
+class EntityManager {
+private:
+       /** The array of all entities. */
+       Container<EntityData> entities;
+
+public:
+       // max is not enforced
+       EntityManager()
+               /*: entities()*/ {}
+
+       ~EntityManager(void) {
+               entities.clear();
+       }
+
+       /**
+        * Creates a new entity.
+        * @return an Entity object for the new entity
+        */
+       Entity create(void) {
+               static Id newId = 0;
+
+               entities.emplace_back(newId);
+               return Entity(entities.begin() + entities.size() - 1);
+       }
+
+       /**
+        * Kills (removes) an entity.
+        * @param e the entity to remove
+        */
+       void kill(const Entity& e) {
+               if (e.pos > entities.begin() && e.pos < entities.end())
+                       entities.erase(e.pos);
+       }
+
+       /**
+        * Destroys all entities.
+        */
+       void reset(void) {
+               entities.clear();
+       }
+
+       /**
+        * Runs a function through all entities.
+        * @param f the function to run through
+        */
+       void each(std::function<void(Entity e)> f) {
+               for (auto i = entities.begin(); i < entities.end(); ++i)
+                       f(Entity(i));
+       }
+
+       template<class T1>
+       void trySort(void) {
+               static unsigned int oldSize = 0;
+               if (entities.size() < 100000 && entities.size() != oldSize) {
+                       oldSize = entities.size();
+
+                       std::sort(entities.begin(), entities.end(),
+                       [](auto&& e1, auto&& e2){
+                                       return Entity(e1).hasComponent<T1>() && !Entity(e2).hasComponent<T1>();
+                       });
+               }
+       }
+
+       /**
+        * Runs a function through all entities with the given components.
+        * @param f the function to run through
+        */
+       template<class T1>
+       void each(std::function<void(Entity e)> f) {
+               trySort<T1>();
+               bool good = false;
+               for (auto i = entities.begin(); i < entities.end(); ++i) {
+                       Entity en (i);
+                       if (en.hasComponent<T1>()) {
+                               f(en);
+                               good = true;
+                       } else if (good) break;
+               }
+       }
+
+       template<class T1, class T2>
+       void each(std::function<void(Entity e)> f) {
+               trySort<T1>();
+               bool good = false;
+               for (auto i = entities.begin(); i < entities.end(); ++i) {
+                       Entity en (i);
+                       if (en.hasComponent<T1>() && en.hasComponent<T2>()) {
+                               f(en);
+                               good = true;
+                       } else if (good) break;
+               }
+       }
+};
+
+
+
+using DeltaTime = int;
+
+class System {
+public:
+       virtual void update(EntityManager& em, DeltaTime dt) = 0;
+};
+
+class SystemManager {
+private:
+       Container<System*> systems;
+       EntityManager& entities;
+
+public:
+       SystemManager(EntityManager& em)
+               : entities(em) {}
+
+       template<class T, typename... Args>
+       void add(Args... args) {
+               static_assert(std::is_convertible<T*, System*>::value,
+                       "systems must inherit System base class");
+               systems.push_back(new T(args...));
+       }
+
+       template<class T>
+       void update(DeltaTime dt) {
+               static_assert(std::is_convertible<T*, System*>::value,
+                       "systems must inherit System base class");
+               for (auto s : systems) {
+                       if (dynamic_cast<T*>(s)) {
+                               s->update(entities, dt);
+                               return;
+                       }
+               }
+       }
+};
+
+#endif // ENTITIES_HPP_
diff --git a/entitiesBenchmark.h b/entitiesBenchmark.h
new file mode 100644 (file)
index 0000000..dcf22f1
--- /dev/null
@@ -0,0 +1,140 @@
+#ifndef ENTITIESBENCHMARK_H_
+#define ENTITIESBENCHMARK_H_
+
+#include <string>
+#include <vector>
+#include <memory>
+#include <random>
+#include <numeric>
+#include <functional>
+
+#include "entities.hpp"
+
+class EntitiesBenchmark {
+    public:
+
+    struct PositionComponent : public Component {
+        float x = 0.0f;
+        float y = 0.0f;
+    };
+
+    struct VelocityComponent : public Component {
+        float x = 0.0f;
+        float y = 0.0f;
+    };
+
+    struct ComflabulationComponent : public Component {
+        float thingy = 0.0;
+        int dingy = 0;
+        bool mingy = false;
+        std::string stringy;
+    };
+
+    class MovementSystem : public System {
+        public:
+        MovementSystem() = default;
+
+        void update(EntityManager &es, DeltaTime dt) {
+                       es.each<PositionComponent, VelocityComponent>(
+                               [dt](Entity e) {
+                                       auto& pos = *e.component<PositionComponent>();
+                                       auto& vel = *e.component<VelocityComponent>();
+                                       pos.x = vel.x * dt;
+                                       pos.y = vel.y * dt;
+                               }
+                       );
+        }
+    };
+
+    class ComflabSystem : public System {
+        public:
+        ComflabSystem() = default;
+
+        void update(EntityManager &es, DeltaTime dt) {
+                       es.each<ComflabulationComponent>(
+                               [dt](Entity e) {
+                                       auto comflab = e.component<ComflabulationComponent>();
+                       comflab->thingy *= 1.000001f;
+                       comflab->mingy = !comflab->mingy;
+                       comflab->dingy++;
+                       //comflab.stringy = std::to_string(comflab.dingy);
+                   }
+                       );
+        }
+    };
+
+    #ifdef USE_MORECOMPLEX_SYSTEM
+    class MoreComplexSystem : public System {
+        private:
+        int random(int min, int max){
+            // Seed with a real random value, if available
+            static std::random_device r;
+        
+            // Choose a random mean between min and max
+            static std::default_random_engine e1(r());
+
+            std::uniform_int_distribution<int> uniform_dist(min, max);
+
+            return uniform_dist(e1);
+        }
+
+        public:
+        MoreComplexSystem() = default;
+
+        void update(EntityManager &es, DeltaTime dt) {
+                       es.each<PositionComponent, DirectionComponent, ComflabulationComponent>(
+                               [dt](Entity e) {
+                                       auto comflab = e.component<ComflabulationComponent>();
+                                       if(comflab) {
+                                               std::vector<double> vec;
+                                               for(size_t i = 0; i < comflab->dingy && i < 100; i++)
+                                                       vec.push_back(i * comflab0>thingy);
+                                               int sum = std::accumulate(vec.begin(), vec.end(), 0);
+                                               int product = std::accumulate(vec.begin(), vec.end(),
+                                                       1, std::multiplies<double>());
+                                               comflab->stringy = std::to_string(comflab->dingy);
+                                               
+                                               auto pos = e.component<PositionComponent>();
+                                               auto vel = e.component<VelocityComponent>();
+                                               if (pos && vel && comflab->dingy % 10000 == 0) {
+                                                       if (pos->x > pos->y) {
+                                                               vel->x = random(0, 5);
+                                                               vel->y = random(0, 10);
+                                                       } else {
+                                                               vel->x = random(0, 10);
+                                                               vel->y = random(0, 5);
+                                                       }
+                                               }
+                                       }
+                               }
+                       );
+        }
+    };
+    #endif
+
+    class Application {
+        public:
+               EntityManager em;
+               SystemManager sm;
+
+        Application() : sm(em) {
+            sm.add<MovementSystem>();
+            sm.add<ComflabSystem>();
+            #ifdef USE_MORECOMPLEX_SYSTEM
+            sm.add<MoreComplexSystem>();
+            #endif
+        }
+
+        void update(DeltaTime dt) {
+            sm.update<MovementSystem>(dt);
+            sm.update<ComflabSystem>(dt);
+            #ifdef USE_MORECOMPLEX_SYSTEM
+            sm.update<MoreComplexSystem>(dt);
+            #endif
+        }
+    };
+
+    static constexpr double fakeDeltaTime = 1.0 / 60;
+};
+
+#endif // ENTITIESBENCHMARK_H_
diff --git a/main.cpp b/main.cpp
new file mode 100644 (file)
index 0000000..0d77c8d
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,207 @@
+#include <iostream>
+#include <string>
+#include <ctime>
+#include <functional>
+#include <iomanip>
+
+#include "entities.hpp"
+
+#include <entityx/entityx.h>
+#include <entityx/deps/Dependencies.h>
+
+using TestFunc = std::function<void(void)>;
+constexpr unsigned int testCount = 100;
+
+void test(const std::string& t, TestFunc ours, TestFunc theirs)
+{
+       auto run = [](auto f) {
+               clock_t avg = 0;
+               for (unsigned int i = 0; i < testCount; i++) {
+                       auto start = clock();
+                       f();
+                       auto diff = clock() - start;
+                       avg += diff;
+               }
+               auto diff = avg / testCount;
+               std::cout << "Test took " <<
+                       diff << " ticks (" << diff / static_cast<double>(CLOCKS_PER_SEC) <<
+                       "ms).\n";
+               return diff;
+       };
+
+       std::cout << std::fixed << std::setprecision(6);
+       std::cout << "==============================================\n";
+       std::cout << "Running test: " << t << "\n";
+
+       std::cout << "Ours:\n";
+       auto o = run(ours);
+
+       std::cout << "Theirs:\n";
+       auto p = run(theirs);
+
+       auto percent = (o - p) / static_cast<float>(p);
+       std::cout << "\nDIFFERENCE: " << o - p << " ticks (" <<
+               std::setprecision(2) << percent << "%).\n\n";
+}
+
+struct Position : public Component {
+       float x, y;
+
+       Position(float _x = 0, float _y = 0)
+               : x(_x), y(_y) {}
+};
+
+struct Velocity : public Component {
+       float x, y;
+       Velocity(float _x = 0, float _y = 0)
+               : x(_x), y(_y) {}
+};
+
+int main(void)
+{
+       EntityManager em;
+
+       entityx::EventManager ev;
+       entityx::EntityManager xm (ev);
+       entityx::SystemManager xsm (xm, ev);
+
+       test("Creation500",
+       [&em](){
+               for (int i = 0; i < 500; i++)
+                       em.create();
+       },
+       [&xm](){
+               for (int i = 0; i < 500; i++)
+                       xm.create();
+       });
+
+       test("Reset",
+       [&em](){
+               em.reset();
+       },
+       [&xm](){
+               xm.reset();
+       });
+
+       test("Assign",
+       [&em](){
+               auto e = em.create();
+               auto& pos = *e.assign<Position>(12, 34);
+
+               //std::cout << pos.x << ' ' << pos.y << '\n';
+               pos.x = 56, pos.y = 16;
+               //std::cout << pos.x << ' ' << pos.y << '\n';
+       },
+       [&xm](){
+               auto e = xm.create();
+               auto pos = e.assign<Position>(12, 34);
+
+               //std::cout << pos->x << ' ' << pos->y << '\n';
+               pos->x = 56, pos->y = 16;
+               //std::cout << pos->x << ' ' << pos->y << '\n';
+
+       });
+
+       test("HasComponent",
+       [&em](){
+               auto e = em.create();
+               //std::cout << e.hasComponent<Position>() << '\n';
+               e.assign<Position>();
+               //std::cout << e.hasComponent<Position>() << '\n';
+       },
+       [&xm](){
+               auto e = xm.create();
+               //std::cout << e.has_component<Position>() << '\n';
+               e.assign<Position>();
+               //std::cout << e.has_component<Position>() << '\n';
+       });
+
+       test("Remove",
+       [&em](){
+               auto e = em.create();   
+               //std::cout << e.hasComponent<Position>() << '\n';
+               e.assign<Position>();
+               //std::cout << e.hasComponent<Position>() << '\n';
+               e.remove<Position>();
+               //std::cout << e.hasComponent<Position>() << '\n';
+       },
+       [&xm](){
+               auto e = xm.create();   
+               //std::cout << e.has_component<Position>() << '\n';
+               e.assign<Position>();
+               //std::cout << e.has_component<Position>() << '\n';
+               e.remove<Position>();
+               //std::cout << e.has_component<Position>() << '\n';
+       });
+
+       /*test("EachAll", [&em](){
+               em.reset();
+               em.create(), em.create(), em.create();
+               int i = 0;
+               em.each([&i](Entity e) {
+                       std::cout << ++i << '\n';
+               });
+       });*/
+
+       test("EachSpec",
+       [&em](){
+               em.reset();
+               em.create(), em.create();
+               auto e1 = em.create(); e1.assign<Position>(1, 3);
+               em.create();
+               auto e2 = em.create(); e2.assign<Position>(99);
+
+               em.each<Position>([](Entity e) {
+                       auto& pos = *e.component<Position>();
+                       //std::cout << pos.x << ' ' << pos.y << '\n';
+               });
+       },
+       [&xm](){
+               xm.reset();
+               xm.create(), xm.create();
+               auto e1 = xm.create(); e1.assign<Position>(1, 3);
+               xm.create();
+               auto e2 = xm.create(); e2.assign<Position>(99);
+
+               xm.each<Position>([](entityx::Entity e, Position& pos) {
+                       //std::cout << pos.x << ' ' << pos.y << '\n';
+               });
+       });
+
+       test("Loop100",
+       [&em](){
+               for (int i = 100; i--;) {
+                       auto e = em.create();
+                       e.assign<Position>(i, i);
+               }
+               em.each<Position>([](Entity e) {
+                       auto& pos = * e.component<Position>();
+                       pos.x += 5, pos.y -= 5;
+               });
+       },
+       [&xm](){
+               for (int i = 100; i--;) {
+                       auto e = xm.create();
+                       e.assign<Position>(i, i);
+               }
+               xm.each<Position>([](entityx::Entity e, Position pos) {
+                       pos.x += 5, pos.y -= 5;
+               });
+       });
+
+       /*test("Dependency",
+       [&em](){
+               em.addDependency<Velocity, Position>();
+               auto e = em.create();
+               e.assign<Velocity>();
+               std::cout << e.hasComponent<Position>() << e.hasComponent<Velocity>() << '\n';
+       },
+       [&xm, &xsm](){
+               xsm.add<entityx::deps::Dependency<Velocity, Position>>();
+               auto e = xm.create();
+               e.assign<Velocity>();
+               std::cout << e.has_component<Position>() << e.has_component<Velocity>() << '\n';
+       });*/
+
+       return 0;
+}
diff --git a/pvector.hpp b/pvector.hpp
new file mode 100644 (file)
index 0000000..8fb2f1b
--- /dev/null
@@ -0,0 +1,113 @@
+#ifndef PVECTOR_HPP_
+#define PVECTOR_HPP_
+
+constexpr unsigned int pvectorSizeChange = 32;
+
+template<class T>
+class pvector {
+private:
+       T* items;
+       unsigned int _size;
+       unsigned int capacity;
+
+public:
+       class iterator {
+       private:
+               T* pos;
+       public:
+               iterator(T* p)
+                       : pos(p) {}
+
+               T operator*(void) const {
+                       return *pos;
+               }
+
+               template<typename N>
+               iterator operator+(N inc) const {
+                       return pos + inc;       
+               }
+
+               iterator& operator++(void) {
+                       ++pos;
+                       return *this;
+               }
+
+               template<typename N>
+               iterator operator-(N inc) const {
+                       return pos - inc;
+               }
+
+               bool operator>(const iterator& i) const {
+                       return pos > i.pos;
+               }
+
+               bool operator<(const iterator& i) const {
+                       return pos < i.pos;
+               }
+
+               bool operator!=(const iterator& i) const {
+                       return pos != i.pos;
+               }
+       };
+
+       pvector(void)
+               : items(new T[pvectorSizeChange]), _size(0),
+               capacity(pvectorSizeChange) {}
+
+       ~pvector(void) {
+               delete items;
+       }
+
+       inline iterator begin(void) {
+               return iterator(items);
+       }
+
+       inline iterator end(void) {
+               return iterator(items + _size);
+       }
+
+       inline unsigned int size(void) const {
+               return _size;
+       }
+
+       T& push_back(T& t) {
+               if (_size >= capacity) {
+                       auto nItems = new T[capacity + pvectorSizeChange];
+                       for (unsigned int i = 0; i < capacity; i++)
+                               nItems[i] = items[i];
+                       delete items;
+                       items = nItems;
+               }
+
+               auto& r = items[_size++] = t;
+               return r;
+       }
+
+       template<typename... Args>
+       T& emplace_back(Args... args) {
+               if (_size >= capacity) {
+                       auto nItems = new T[capacity + pvectorSizeChange];
+                       for (unsigned int i = 0; i < capacity; i++)
+                               nItems[i] = items[i];
+                       delete items;
+                       items = nItems;
+               }
+               auto& r = items[_size++] = T(args...);
+               return r;
+       }
+
+       void erase(iterator it) {
+               for (unsigned int i = it - begin(); i < capacity; i++)
+                       items[i] = items[i + 1];
+               _size--;
+       }
+
+       void clear(void) {
+               delete items;
+               items = new T[pvectorSizeChange];
+               _size = 0;
+               capacity = pvectorSizeChange;
+       }
+};
+
+#endif // PVECTOR_HPP_