From: Clyne Sullivan Date: Sun, 26 Feb 2017 23:58:25 +0000 (-0500) Subject: reorginization X-Git-Url: https://code.bitgloo.com/?a=commitdiff_plain;h=f4c47aa1845e996dea89ed17c2d71752df50ed50;p=clyne%2Fentities.git reorginization --- diff --git a/EntityXBenchmark.h b/EntityXBenchmark.h deleted file mode 100644 index defa37c..0000000 --- a/EntityXBenchmark.h +++ /dev/null @@ -1,155 +0,0 @@ -#ifndef ENTITYXBENCHMARK_H_ -#define ENTITYXBENCHMARK_H_ - -#include -#include -#include -#include -#include -#include - -#include "entityx/entityx.h" - -class EntityXBenchmark { - public: - - struct PositionComponent { - float x = 0.0f; - float y = 0.0f; - }; - - struct DirectionComponent { - float x = 0.0f; - float y = 0.0f; - }; - - struct ComflabulationComponent { - float thingy = 0.0; - int dingy = 0; - bool mingy = false; - std::string stringy; - }; - - - // Convenience types for our entity system. - using Entity = entityx::Entity; - using EntityManager = entityx::EventManager; - using EventManager = entityx::EventManager; - - template - using Component = entityx::ComponentHandle; - - using TimeDelta = entityx::TimeDelta; - - - - template - using System = entityx::System; - - - class MovementSystem : public System { - public: - MovementSystem() = default; - - void update(entityx::EntityManager &es, entityx::EventManager &events, entityx::TimeDelta dt) override { - Component position; - Component direction; - - for (auto entity : es.entities_with_components(position, direction)) { - position->x += direction->x * dt; - position->y += direction->y * dt; - } - } - }; - - class ComflabSystem : public System { - public: - ComflabSystem() = default; - - void update(entityx::EntityManager &es, entityx::EventManager &events, entityx::TimeDelta dt) override { - Component comflab; - - for (auto entity : es.entities_with_components(comflab)) { - 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 uniform_dist(min, max); - - return uniform_dist(e1); - } - - public: - MoreComplexSystem() = default; - - void update(entityx::EntityManager &es, entityx::EventManager &events, entityx::TimeDelta dt) override { - Component position; - Component direction; - Component comflab; - - for (auto entity : es.entities_with_components(comflab, direction, comflab)) { - if(comflab) { - std::vector vec; - for(size_t i = 0;i < comflab->dingy && i < 100;i++){ - vec.push_back(i * comflab->thingy); - } - - int sum = std::accumulate(std::begin(vec), std::end(vec), 0.0); - int product = std::accumulate(std::begin(vec), std::end(vec), 1, std::multiplies()); - - comflab->stringy = std::to_string(comflab->dingy); - - if(position && direction && comflab->dingy % 10000 == 0) { - if(position->x > position->y) { - direction->x = random(0, 5); - direction->y = random(0, 10); - } else { - direction->x = random(0, 10); - direction->y = random(0, 5); - } - } - } - } - } - }; - #endif - - class Application : public entityx::EntityX { - public: - Application() { - this->systems.add(); - this->systems.add(); - #ifdef USE_MORECOMPLEX_SYSTEM - this->systems.add(); - #endif - - this->systems.configure(); - } - - void update(TimeDelta dt) { - this->systems.update(dt); - this->systems.update(dt); - #ifdef USE_MORECOMPLEX_SYSTEM - this->systems.update(dt); - #endif - } - }; - - static constexpr double fakeDeltaTime = 1.0 / 60; -}; - -#endif // ENTITYXBENCHMARK_H_ diff --git a/Makefile b/Makefile deleted file mode 100644 index b1a2703..0000000 --- a/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -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 - g++ -std=c++17 -Wall -Wextra xtest.cpp -o xtest -O1 -lentityx - diff --git a/README.md b/README.md index 5ee9502..0265181 100644 --- a/README.md +++ b/README.md @@ -7,43 +7,43 @@ The file that actually contains the library is entities.hpp. ## compared to EntityX entities: ``` -entityx create destroy entity with components 5000000 254 ns/op - [25] entityx 25 entities component systems update 1000000 1572 ns/op - [50] entityx 50 entities component systems update 500000 3009 ns/op - [100] entityx 100 entities component systems update 200000 5995 ns/op - [200] entityx 200 entities component systems update 100000 11848 ns/op - [400] entityx 400 entities component systems update 50000 23631 ns/op - [800] entityx 800 entities component systems update 20000 51669 ns/op - [1600] entityx 1600 entities component systems update 10000 112446 ns/op - [3200] entityx 3200 entities component systems update 5000 225504 ns/op - [5000] entityx 5000 entities component systems update 5000 368282 ns/op - [10000] entityx 10000 entities component systems update 1000 1240502 ns/op - [30000] entityx 30000 entities component systems update 200 5196731 ns/op - [100000] entityx 100000 entities component systems update 100 18331731 ns/op - [500000] entityx 500000 entities component systems update 20 93182044 ns/op - [1000000] entityx 1000000 entities component systems update 10 191315282 ns/op - [2000000] entityx 2000000 entities component systems update 5 299790721 ns/op -./test 43.175s +entities create destroy entity with components 5000000 279 ns/op + [25] entities 25 entities component systems update 1000000 1610 ns/op + [50] entities 50 entities component systems update 500000 3098 ns/op + [100] entities 100 entities component systems update 200000 6247 ns/op + [200] entities 200 entities component systems update 100000 12274 ns/op + [400] entities 400 entities component systems update 50000 25029 ns/op + [800] entities 800 entities component systems update 20000 55717 ns/op + [1600] entities 1600 entities component systems update 10000 116305 ns/op + [3200] entities 3200 entities component systems update 5000 244461 ns/op + [5000] entities 5000 entities component systems update 5000 463835 ns/op + [10000] entities 10000 entities component systems update 1000 1647042 ns/op + [30000] entities 30000 entities component systems update 200 5784773 ns/op + [100000] entities 100000 entities component systems update 50 20711117 ns/op + [500000] entities 500000 entities component systems update 20 103582654 ns/op + [1000000] entities 1000000 entities component systems update 10 208073004 ns/op + [2000000] entities 2000000 entities component systems update 5 341977621 ns/op +./entitiesTests 45.328s ``` EntityX: ``` -entityx create destroy entity with components 5000000 200 ns/op - [25] entityx 25 entities component systems update 500000 2903 ns/op - [50] entityx 50 entities component systems update 200000 5721 ns/op - [100] entityx 100 entities component systems update 100000 11338 ns/op - [200] entityx 200 entities component systems update 50000 22622 ns/op - [400] entityx 400 entities component systems update 50000 45560 ns/op - [800] entityx 800 entities component systems update 20000 92718 ns/op - [1600] entityx 1600 entities component systems update 10000 180159 ns/op - [3200] entityx 3200 entities component systems update 5000 360127 ns/op - [5000] entityx 5000 entities component systems update 2000 563316 ns/op - [10000] entityx 10000 entities component systems update 1000 1130659 ns/op - [30000] entityx 30000 entities component systems update 500 3431964 ns/op - [100000] entityx 100000 entities component systems update 100 11680312 ns/op - [500000] entityx 500000 entities component systems update 20 59996331 ns/op - [1000000] entityx 1000000 entities component systems update 10 128663563 ns/op - [2000000] entityx 2000000 entities component systems update 5 271582063 ns/op -./xtest 41.912s +entityx create destroy entity with components 5000000 212 ns/op + [25] entityx 25 entities component systems update 500000 3025 ns/op + [50] entityx 50 entities component systems update 200000 6274 ns/op + [100] entityx 100 entities component systems update 100000 12101 ns/op + [200] entityx 200 entities component systems update 50000 24227 ns/op + [400] entityx 400 entities component systems update 50000 48100 ns/op + [800] entityx 800 entities component systems update 20000 95872 ns/op + [1600] entityx 1600 entities component systems update 10000 196251 ns/op + [3200] entityx 3200 entities component systems update 5000 397806 ns/op + [5000] entityx 5000 entities component systems update 2000 623035 ns/op + [10000] entityx 10000 entities component systems update 1000 1279807 ns/op + [30000] entityx 30000 entities component systems update 500 3791401 ns/op + [100000] entityx 100000 entities component systems update 100 13074271 ns/op + [500000] entityx 500000 entities component systems update 20 65787539 ns/op + [1000000] entityx 1000000 entities component systems update 10 137753731 ns/op + [2000000] entityx 2000000 entities component systems update 5 290476228 ns/op +./entityXTests 45.232s ``` You can find EntityX [here](https://github.com/alecthomas/entityx). diff --git a/bench.cpp b/bench.cpp deleted file mode 100644 index 99824ac..0000000 --- a/bench.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#include -#include -#include -#include - -#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(); - entity.assign(); - - if (i % 2) { - entity.assign(); - } - } -} - -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(); - entity.assign(); - entity.assign(); - - entities.kill(entity); - } -}) - - - - - -class BenchmarksEntityX { - public: - static const std::vector ENTITIES; - - static inline void makeBenchmarks(std::string name) { - makeBenchmarks(name, ENTITIES); - } - - static void makeBenchmarks(std::string name, const std::vector& 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 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 deleted file mode 100644 index cb1bff4..0000000 --- a/benchpress.hpp +++ /dev/null @@ -1,450 +0,0 @@ -/* -* 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 // max, min -#include // atomic_intmax_t -#include // high_resolution_timer, duration -#include // function -#include // setw -#include // cout -#include // regex, regex_match -#include // stringstream -#include // string -#include // thread -#include // 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 d_func; - -public: - benchmark_info(std::string name, std::function func) - : d_name(name) - , d_func(func) - {} - - std::string get_name() const { return d_name; } - std::function 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 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 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 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 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 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(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 f) { - parallel_context pc(d_num_iterations); - std::vector 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 - 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 - 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() - ->default_value(".*")) - ("benchtime", "run enough iterations of each benchmark to take t seconds", cxxopts::value() - ->default_value("1")) - ("cpu", "specify the number of threads to use for parallel benchmarks", cxxopts::value() - ->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()); - } - if (cmd_opts.count("benchtime")) { - bench_opts.benchtime(cmd_opts["benchtime"].as()); - } - if (cmd_opts.count("cpu")) { - bench_opts.cpu(cmd_opts["cpu"].as()); - } - 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::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 deleted file mode 100644 index 047190e..0000000 --- a/cxxopts.hpp +++ /dev/null @@ -1,1312 +0,0 @@ -/* - -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 -#include -#include -#include -#include -#include -#include -#include - -//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 can be found by the -//compiler, and that icu-uc is linked in to the binary. - -#ifdef CXXOPTS_USE_UNICODE -#include - -namespace cxxopts -{ - typedef icu::UnicodeString String; - - inline - String - toLocalString(std::string s) - { - return icu::UnicodeString::fromUTF8(s); - } - - class UnicodeStringIterator : public - std::iterator - { - 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 - 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 - 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 - String& - stringAppend(String& s, Iterator begin, Iterator end) - { - return s.append(begin, end); - } - - template - std::string - toUTF8String(T&& t) - { - return std::forward(t); - } - -} - -//ifdef CXXOPTS_USE_UNICODE -#endif - -namespace cxxopts -{ - class Value : public std::enable_shared_from_this - { - 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 - default_value(const std::string& value) = 0; - - virtual std::shared_ptr - 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 - 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 - void - parse_value(const std::string& text, std::vector& 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 - struct value_has_arg - { - static constexpr bool value = true; - }; - - template <> - struct value_has_arg - { - static constexpr bool value = false; - }; - - template - class standard_value : public Value - { - public: - standard_value() - : m_result(std::make_shared()) - , 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::value; - } - - bool - has_default() const - { - return m_default; - } - - bool - has_implicit() const - { - return m_implicit; - } - - virtual std::shared_ptr - default_value(const std::string& value){ - m_default = true; - m_default_value = value; - return shared_from_this(); - } - - virtual std::shared_ptr - 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 m_result; - T* m_store; - bool m_default = false; - std::string m_default_value; - bool m_implicit = false; - std::string m_implicit_value; - }; - } - - template - std::shared_ptr - value() - { - return std::make_shared>(); - } - - template - std::shared_ptr - value(T& t) - { - return std::make_shared>(&t); - } - - class OptionAdder; - - class OptionDetails - { - public: - OptionDetails - ( - const String& description, - std::shared_ptr 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 - const T& - as() const - { -#ifdef CXXOPTS_NO_RTTI - return static_cast&>(*m_value).get(); -#else - return dynamic_cast&>(*m_value).get(); -#endif - } - - private: - String m_desc; - std::shared_ptr 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 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 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& groups = {""}) const; - - inline - const std::vector - 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 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 value, - const std::string& name, - const std::string& arg = "" - ); - - inline - void - checked_parse_arg - ( - int argc, - char* argv[], - int& current, - std::shared_ptr 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> m_options; - std::string m_positional; - - //mapping from groups to help options - std::map 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 value - = ::cxxopts::value(), - 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 option_matcher - ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([a-zA-Z]+)"); - - std::basic_regex 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 value, - std::string arg_help -) -{ - std::match_results 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 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 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 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 value, - std::string arg_help -) -{ - auto stringDesc = toLocalString(std::move(desc)); - auto option = std::make_shared(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 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> 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(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& 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 -Options::groups() const -{ - std::vector g; - - std::transform( - m_help.begin(), - m_help.end(), - std::back_inserter(g), - [] (const std::map::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/entitiesBenchmark.h b/entitiesBenchmark.h deleted file mode 100644 index dcf22f1..0000000 --- a/entitiesBenchmark.h +++ /dev/null @@ -1,140 +0,0 @@ -#ifndef ENTITIESBENCHMARK_H_ -#define ENTITIESBENCHMARK_H_ - -#include -#include -#include -#include -#include -#include - -#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( - [dt](Entity e) { - auto& pos = *e.component(); - auto& vel = *e.component(); - 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( - [dt](Entity e) { - auto comflab = e.component(); - 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 uniform_dist(min, max); - - return uniform_dist(e1); - } - - public: - MoreComplexSystem() = default; - - void update(EntityManager &es, DeltaTime dt) { - es.each( - [dt](Entity e) { - auto comflab = e.component(); - if(comflab) { - std::vector 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()); - comflab->stringy = std::to_string(comflab->dingy); - - auto pos = e.component(); - auto vel = e.component(); - 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(); - sm.add(); - #ifdef USE_MORECOMPLEX_SYSTEM - sm.add(); - #endif - } - - void update(DeltaTime dt) { - sm.update(dt); - sm.update(dt); - #ifdef USE_MORECOMPLEX_SYSTEM - sm.update(dt); - #endif - } - }; - - static constexpr double fakeDeltaTime = 1.0 / 60; -}; - -#endif // ENTITIESBENCHMARK_H_ diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 0d77c8d..0000000 --- a/main.cpp +++ /dev/null @@ -1,207 +0,0 @@ -#include -#include -#include -#include -#include - -#include "entities.hpp" - -#include -#include - -using TestFunc = std::function; -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(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(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(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(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() << '\n'; - e.assign(); - //std::cout << e.hasComponent() << '\n'; - }, - [&xm](){ - auto e = xm.create(); - //std::cout << e.has_component() << '\n'; - e.assign(); - //std::cout << e.has_component() << '\n'; - }); - - test("Remove", - [&em](){ - auto e = em.create(); - //std::cout << e.hasComponent() << '\n'; - e.assign(); - //std::cout << e.hasComponent() << '\n'; - e.remove(); - //std::cout << e.hasComponent() << '\n'; - }, - [&xm](){ - auto e = xm.create(); - //std::cout << e.has_component() << '\n'; - e.assign(); - //std::cout << e.has_component() << '\n'; - e.remove(); - //std::cout << e.has_component() << '\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(1, 3); - em.create(); - auto e2 = em.create(); e2.assign(99); - - em.each([](Entity e) { - auto& pos = *e.component(); - //std::cout << pos.x << ' ' << pos.y << '\n'; - }); - }, - [&xm](){ - xm.reset(); - xm.create(), xm.create(); - auto e1 = xm.create(); e1.assign(1, 3); - xm.create(); - auto e2 = xm.create(); e2.assign(99); - - xm.each([](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(i, i); - } - em.each([](Entity e) { - auto& pos = * e.component(); - pos.x += 5, pos.y -= 5; - }); - }, - [&xm](){ - for (int i = 100; i--;) { - auto e = xm.create(); - e.assign(i, i); - } - xm.each([](entityx::Entity e, Position pos) { - pos.x += 5, pos.y -= 5; - }); - }); - - /*test("Dependency", - [&em](){ - em.addDependency(); - auto e = em.create(); - e.assign(); - std::cout << e.hasComponent() << e.hasComponent() << '\n'; - }, - [&xm, &xsm](){ - xsm.add>(); - auto e = xm.create(); - e.assign(); - std::cout << e.has_component() << e.has_component() << '\n'; - });*/ - - return 0; -} diff --git a/tests/EntityXBenchmark.h b/tests/EntityXBenchmark.h new file mode 100644 index 0000000..defa37c --- /dev/null +++ b/tests/EntityXBenchmark.h @@ -0,0 +1,155 @@ +#ifndef ENTITYXBENCHMARK_H_ +#define ENTITYXBENCHMARK_H_ + +#include +#include +#include +#include +#include +#include + +#include "entityx/entityx.h" + +class EntityXBenchmark { + public: + + struct PositionComponent { + float x = 0.0f; + float y = 0.0f; + }; + + struct DirectionComponent { + float x = 0.0f; + float y = 0.0f; + }; + + struct ComflabulationComponent { + float thingy = 0.0; + int dingy = 0; + bool mingy = false; + std::string stringy; + }; + + + // Convenience types for our entity system. + using Entity = entityx::Entity; + using EntityManager = entityx::EventManager; + using EventManager = entityx::EventManager; + + template + using Component = entityx::ComponentHandle; + + using TimeDelta = entityx::TimeDelta; + + + + template + using System = entityx::System; + + + class MovementSystem : public System { + public: + MovementSystem() = default; + + void update(entityx::EntityManager &es, entityx::EventManager &events, entityx::TimeDelta dt) override { + Component position; + Component direction; + + for (auto entity : es.entities_with_components(position, direction)) { + position->x += direction->x * dt; + position->y += direction->y * dt; + } + } + }; + + class ComflabSystem : public System { + public: + ComflabSystem() = default; + + void update(entityx::EntityManager &es, entityx::EventManager &events, entityx::TimeDelta dt) override { + Component comflab; + + for (auto entity : es.entities_with_components(comflab)) { + 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 uniform_dist(min, max); + + return uniform_dist(e1); + } + + public: + MoreComplexSystem() = default; + + void update(entityx::EntityManager &es, entityx::EventManager &events, entityx::TimeDelta dt) override { + Component position; + Component direction; + Component comflab; + + for (auto entity : es.entities_with_components(comflab, direction, comflab)) { + if(comflab) { + std::vector vec; + for(size_t i = 0;i < comflab->dingy && i < 100;i++){ + vec.push_back(i * comflab->thingy); + } + + int sum = std::accumulate(std::begin(vec), std::end(vec), 0.0); + int product = std::accumulate(std::begin(vec), std::end(vec), 1, std::multiplies()); + + comflab->stringy = std::to_string(comflab->dingy); + + if(position && direction && comflab->dingy % 10000 == 0) { + if(position->x > position->y) { + direction->x = random(0, 5); + direction->y = random(0, 10); + } else { + direction->x = random(0, 10); + direction->y = random(0, 5); + } + } + } + } + } + }; + #endif + + class Application : public entityx::EntityX { + public: + Application() { + this->systems.add(); + this->systems.add(); + #ifdef USE_MORECOMPLEX_SYSTEM + this->systems.add(); + #endif + + this->systems.configure(); + } + + void update(TimeDelta dt) { + this->systems.update(dt); + this->systems.update(dt); + #ifdef USE_MORECOMPLEX_SYSTEM + this->systems.update(dt); + #endif + } + }; + + static constexpr double fakeDeltaTime = 1.0 / 60; +}; + +#endif // ENTITYXBENCHMARK_H_ diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..bf69813 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,4 @@ +all: + g++ -std=c++17 -Wall -Wextra entitiesTests.cpp -o entitiesTests -O1 + g++ -std=c++17 -Wall -Wextra entityXTests.cpp -o entityXTests -O1 -lentityx + diff --git a/tests/benchpress.hpp b/tests/benchpress.hpp new file mode 100644 index 0000000..cb1bff4 --- /dev/null +++ b/tests/benchpress.hpp @@ -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 // max, min +#include // atomic_intmax_t +#include // high_resolution_timer, duration +#include // function +#include // setw +#include // cout +#include // regex, regex_match +#include // stringstream +#include // string +#include // thread +#include // 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 d_func; + +public: + benchmark_info(std::string name, std::function func) + : d_name(name) + , d_func(func) + {} + + std::string get_name() const { return d_name; } + std::function 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 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 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 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 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 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(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 f) { + parallel_context pc(d_num_iterations); + std::vector 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 + 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 + 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() + ->default_value(".*")) + ("benchtime", "run enough iterations of each benchmark to take t seconds", cxxopts::value() + ->default_value("1")) + ("cpu", "specify the number of threads to use for parallel benchmarks", cxxopts::value() + ->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()); + } + if (cmd_opts.count("benchtime")) { + bench_opts.benchtime(cmd_opts["benchtime"].as()); + } + if (cmd_opts.count("cpu")) { + bench_opts.cpu(cmd_opts["cpu"].as()); + } + 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::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/tests/cxxopts.hpp b/tests/cxxopts.hpp new file mode 100644 index 0000000..047190e --- /dev/null +++ b/tests/cxxopts.hpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +//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 can be found by the +//compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include + +namespace cxxopts +{ + typedef icu::UnicodeString String; + + inline + String + toLocalString(std::string s) + { + return icu::UnicodeString::fromUTF8(s); + } + + class UnicodeStringIterator : public + std::iterator + { + 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 + 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 + 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 + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + return s.append(begin, end); + } + + template + std::string + toUTF8String(T&& t) + { + return std::forward(t); + } + +} + +//ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts +{ + class Value : public std::enable_shared_from_this + { + 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 + default_value(const std::string& value) = 0; + + virtual std::shared_ptr + 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 + 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 + void + parse_value(const std::string& text, std::vector& 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 + struct value_has_arg + { + static constexpr bool value = true; + }; + + template <> + struct value_has_arg + { + static constexpr bool value = false; + }; + + template + class standard_value : public Value + { + public: + standard_value() + : m_result(std::make_shared()) + , 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::value; + } + + bool + has_default() const + { + return m_default; + } + + bool + has_implicit() const + { + return m_implicit; + } + + virtual std::shared_ptr + default_value(const std::string& value){ + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + virtual std::shared_ptr + 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 m_result; + T* m_store; + bool m_default = false; + std::string m_default_value; + bool m_implicit = false; + std::string m_implicit_value; + }; + } + + template + std::shared_ptr + value() + { + return std::make_shared>(); + } + + template + std::shared_ptr + value(T& t) + { + return std::make_shared>(&t); + } + + class OptionAdder; + + class OptionDetails + { + public: + OptionDetails + ( + const String& description, + std::shared_ptr 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 + const T& + as() const + { +#ifdef CXXOPTS_NO_RTTI + return static_cast&>(*m_value).get(); +#else + return dynamic_cast&>(*m_value).get(); +#endif + } + + private: + String m_desc; + std::shared_ptr 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 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 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& groups = {""}) const; + + inline + const std::vector + 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 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 value, + const std::string& name, + const std::string& arg = "" + ); + + inline + void + checked_parse_arg + ( + int argc, + char* argv[], + int& current, + std::shared_ptr 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> m_options; + std::string m_positional; + + //mapping from groups to help options + std::map 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 value + = ::cxxopts::value(), + 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 option_matcher + ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([a-zA-Z]+)"); + + std::basic_regex 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 value, + std::string arg_help +) +{ + std::match_results 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 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 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 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 value, + std::string arg_help +) +{ + auto stringDesc = toLocalString(std::move(desc)); + auto option = std::make_shared(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 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> 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(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& 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 +Options::groups() const +{ + std::vector g; + + std::transform( + m_help.begin(), + m_help.end(), + std::back_inserter(g), + [] (const std::map::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/tests/entitiesBenchmark.h b/tests/entitiesBenchmark.h new file mode 100644 index 0000000..e046fe3 --- /dev/null +++ b/tests/entitiesBenchmark.h @@ -0,0 +1,140 @@ +#ifndef ENTITIESBENCHMARK_H_ +#define ENTITIESBENCHMARK_H_ + +#include +#include +#include +#include +#include +#include + +#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( + [dt](Entity e) { + auto& pos = *e.component(); + auto& vel = *e.component(); + 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( + [dt](Entity e) { + auto comflab = e.component(); + 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 uniform_dist(min, max); + + return uniform_dist(e1); + } + + public: + MoreComplexSystem() = default; + + void update(EntityManager &es, DeltaTime dt) { + es.each( + [dt](Entity e) { + auto comflab = e.component(); + if(comflab) { + std::vector 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()); + comflab->stringy = std::to_string(comflab->dingy); + + auto pos = e.component(); + auto vel = e.component(); + 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(); + sm.add(); + #ifdef USE_MORECOMPLEX_SYSTEM + sm.add(); + #endif + } + + void update(DeltaTime dt) { + sm.update(dt); + sm.update(dt); + #ifdef USE_MORECOMPLEX_SYSTEM + sm.update(dt); + #endif + } + }; + + static constexpr double fakeDeltaTime = 1.0 / 60; +}; + +#endif // ENTITIESBENCHMARK_H_ diff --git a/tests/entitiesTests b/tests/entitiesTests new file mode 100755 index 0000000..719c8e1 Binary files /dev/null and b/tests/entitiesTests differ diff --git a/tests/entitiesTests.cpp b/tests/entitiesTests.cpp new file mode 100644 index 0000000..8ea2b92 --- /dev/null +++ b/tests/entitiesTests.cpp @@ -0,0 +1,173 @@ +#include +#include +#include +#include + +#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(); + entity.assign(); + + if (i % 2) { + entity.assign(); + } + } +} + +inline void runEntitiesSystemsEntitiesBenchmark(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("entities 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(); + entity.assign(); + entity.assign(); + + entities.kill(entity); + } +}) + + + + + +class BenchmarksEntities { + public: + static const std::vector ENTITIES; + + static inline void makeBenchmarks(std::string name) { + makeBenchmarks(name, ENTITIES); + } + + static void makeBenchmarks(std::string name, const std::vector& 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) { + runEntitiesSystemsEntitiesBenchmark(ctx, nentities); + }) + } + } + + BenchmarksEntities(std::string name){ + makeBenchmarks(name); + } +}; +const std::vector BenchmarksEntities::ENTITIES = { + 25, 50, + 100, 200, 400, 800, + 1600, 3200, 5000, + 10'000, 30'000, + 100'000, 500'000, + 1'000'000, 2'000'000 +}; + +BenchmarksEntities entitiesBenchmarks ("entities"); + + + + + +/* +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/tests/entityXTests b/tests/entityXTests new file mode 100755 index 0000000..514d5f4 Binary files /dev/null and b/tests/entityXTests differ diff --git a/tests/entityXTests.cpp b/tests/entityXTests.cpp new file mode 100644 index 0000000..2f814c5 --- /dev/null +++ b/tests/entityXTests.cpp @@ -0,0 +1,174 @@ +#include +#include +#include +#include + +#define BENCHPRESS_CONFIG_MAIN +#include "benchpress.hpp" + +#include + +#include "EntityXBenchmark.h" + +inline void init_entities(entityx::EntityManager& entities, size_t nentities){ + for (size_t i = 0; i < nentities; i++) { + auto entity = entities.create(); + + entity.assign(); + entity.assign(); + + if (i % 2) { + entity.assign(); + } + } +} + +inline void runEntitiesSystemsEntityXBenchmark(benchpress::context* ctx, size_t nentities) { + EntityXBenchmark::Application app; + auto& entities = app.entities; + + init_entities(entities, nentities); + + ctx->reset_timer(); + for (size_t i = 0; i < ctx->num_iterations(); ++i) { + app.update(EntityXBenchmark::fakeDeltaTime); + } +} + + + + +BENCHMARK("entityx create destroy entity with components", [](benchpress::context* ctx) { + entityx::EntityX app; + auto& entities = app.entities; + + ctx->reset_timer(); + for (size_t i = 0; i < ctx->num_iterations(); ++i) { + auto entity = entities.create(); + + entity.assign(); + entity.assign(); + entity.assign(); + + entity.destroy(); + } +}) + + + + + +class BenchmarksEntityX { + public: + static const std::vector ENTITIES; + + static inline void makeBenchmarks(std::string name) { + makeBenchmarks(name, ENTITIES); + } + + static void makeBenchmarks(std::string name, const std::vector& 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 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/xtest.cpp b/xtest.cpp deleted file mode 100644 index 2f814c5..0000000 --- a/xtest.cpp +++ /dev/null @@ -1,174 +0,0 @@ -#include -#include -#include -#include - -#define BENCHPRESS_CONFIG_MAIN -#include "benchpress.hpp" - -#include - -#include "EntityXBenchmark.h" - -inline void init_entities(entityx::EntityManager& entities, size_t nentities){ - for (size_t i = 0; i < nentities; i++) { - auto entity = entities.create(); - - entity.assign(); - entity.assign(); - - if (i % 2) { - entity.assign(); - } - } -} - -inline void runEntitiesSystemsEntityXBenchmark(benchpress::context* ctx, size_t nentities) { - EntityXBenchmark::Application app; - auto& entities = app.entities; - - init_entities(entities, nentities); - - ctx->reset_timer(); - for (size_t i = 0; i < ctx->num_iterations(); ++i) { - app.update(EntityXBenchmark::fakeDeltaTime); - } -} - - - - -BENCHMARK("entityx create destroy entity with components", [](benchpress::context* ctx) { - entityx::EntityX app; - auto& entities = app.entities; - - ctx->reset_timer(); - for (size_t i = 0; i < ctx->num_iterations(); ++i) { - auto entity = entities.create(); - - entity.assign(); - entity.assign(); - entity.assign(); - - entity.destroy(); - } -}) - - - - - -class BenchmarksEntityX { - public: - static const std::vector ENTITIES; - - static inline void makeBenchmarks(std::string name) { - makeBenchmarks(name, ENTITIES); - } - - static void makeBenchmarks(std::string name, const std::vector& 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 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); -}) -*/