diff --git a/doc/build_instructions/ubuntu_16.04.md b/doc/build_instructions/ubuntu_16.04.md index ceb5972cfa..31ef091ced 100644 --- a/doc/build_instructions/ubuntu_16.04.md +++ b/doc/build_instructions/ubuntu_16.04.md @@ -1,5 +1,5 @@ # Prerequisite steps for Ubuntu users (Ubuntu 16.04) - `sudo apt-get update` - - `sudo apt-get install cmake libfreetype6-dev python3-dev python3-pip libepoxy-dev libsdl2-dev libsdl2-image-dev libopusfile-dev libfontconfig1-dev libharfbuzz-dev libpng-dev opus-tools python3-pil python3-numpy python3-pygments qtdeclarative5-dev qml-module-qtquick-controls` + - `sudo apt-get install cmake libfreetype6-dev python3-dev python3-pip libepoxy-dev libsdl2-dev libsdl2-image-dev libopusfile-dev libfontconfig1-dev libharfbuzz-dev libncurses5-dev opus-tools python3-pil python3-numpy python3-pygments qtdeclarative5-dev qml-module-qtquick-controls` - `pip3 install cython` diff --git a/doc/building.md b/doc/building.md index 130109a8d6..e695c97976 100644 --- a/doc/building.md +++ b/doc/building.md @@ -30,6 +30,7 @@ Dependency list: CRA python >=3.4 C cython >=0.25 C cmake >=3.1.0 + CR ncurses A numpy A python imaging library (PIL) -> pillow CR opengl >=2.1 diff --git a/libopenage/CMakeLists.txt b/libopenage/CMakeLists.txt index de5ad13240..a6dd40084d 100644 --- a/libopenage/CMakeLists.txt +++ b/libopenage/CMakeLists.txt @@ -31,14 +31,15 @@ pxdgen( add_subdirectory("audio") add_subdirectory("console") add_subdirectory("coord") +add_subdirectory("curve") add_subdirectory("cvar") add_subdirectory("datastructure") -add_subdirectory("gui") add_subdirectory("error") add_subdirectory("gamestate") +add_subdirectory("gui") add_subdirectory("input") -add_subdirectory("log") add_subdirectory("job") +add_subdirectory("log") add_subdirectory("pathfinding") add_subdirectory("pyinterface") add_subdirectory("renderer") @@ -102,6 +103,9 @@ find_package(Opusfile REQUIRED) find_package(Epoxy REQUIRED) find_package(HarfBuzz 1.0.0 REQUIRED) +set(CURSES_NEED_NCURSES TRUE) +find_package(Curses REQUIRED) + set(QT_VERSION_REQ "5.5") find_package(Qt5Core ${QT_VERSION_REQ} REQUIRED) find_package(Qt5Quick ${QT_VERSION_REQ} REQUIRED) @@ -190,6 +194,7 @@ target_link_libraries(libopenage ${RT_LIB} ${OGG_LIB} ${EXECINFO_LIB} + ${CURSES_LIBRARIES} # TODO: change to PUBLIC (or, alternatively, remove all keywords # of this type) when qt cmake scripts change declarations of the # IMPORTED libraries to GLOBAL. diff --git a/libopenage/curve/CMakeLists.txt b/libopenage/curve/CMakeLists.txt new file mode 100644 index 0000000000..cee2633835 --- /dev/null +++ b/libopenage/curve/CMakeLists.txt @@ -0,0 +1,29 @@ + +add_sources(libopenage + internal/keyframe_container.cpp + internal/value_container.cpp + queue.cpp + continuous.cpp + discrete.cpp + iterator.cpp + map.cpp + map_filter_iterator.cpp + queue.cpp + queue_filter_iterator.cpp +) + +pxdgen( + continuous.h + curve.h + discrete.h + iterator.h + map.h + map_filter_iterator.h + queue.h + queue_filter_iterator.h +) + +add_subdirectory(events) +add_subdirectory(internal) +add_subdirectory(test) +add_subdirectory(demo) diff --git a/libopenage/curve/continuous.cpp b/libopenage/curve/continuous.cpp new file mode 100644 index 0000000000..de78eb3e7f --- /dev/null +++ b/libopenage/curve/continuous.cpp @@ -0,0 +1,10 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "continuous.h" + +namespace openage { +namespace curve { + +// This file is intended to be empty + +}} // openage::curve diff --git a/libopenage/curve/continuous.h b/libopenage/curve/continuous.h new file mode 100644 index 0000000000..166eb22c48 --- /dev/null +++ b/libopenage/curve/continuous.h @@ -0,0 +1,61 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "internal/value_container.h" + +#include "../log/log.h" + +namespace openage { +namespace curve { + +/** + * Continuous Datatype. + * Stores a value container with continuous access. + * The bound template type _T has to implement `operator+(_T)` and + * `operator*(curve_time_t)`. + * + * pxd: + * cppclass Continuous(ValueContainer): + * _T get(const curve_time_t&) except + + */ +template +class Continuous : public ValueContainer<_T> { +public: + using ValueContainer<_T>::ValueContainer; + /** + * will interpolate between the keyframes linearly based on the time. + */ + _T get(const curve_time_t &) const override; +}; + + + +template +_T Continuous<_T>::get(const curve_time_t &time) const { + auto e = this->container.last(time, this->last_element); + this->last_element = e; + auto nxt = e; + ++nxt; + + double diff_time = 0; + double offset = time - e->time; + // If we do not have a next (buffer underrun!!) we assign values + if (nxt == this->container.end()) { + // log::log(WARN << "Continuous buffer underrun. This might be bad! Assuming constant."); + } else { + diff_time = nxt->time - e->time; + } + + if (nxt == this->container.end() // We do not have next - this is bad + || offset == 0 // we do not have an offset - this is performance + || diff_time == 0) { // we do not have diff - this is division-by-zero-error + return e->value; + } else { + // Fraction between time(now) and time(next) that has elapsed + double elapsed_frac = (double)offset / (double)diff_time; + return e->value + (nxt->value - e->value) * elapsed_frac; + } +} + +}} // openage::curve diff --git a/libopenage/curve/curve.cpp b/libopenage/curve/curve.cpp new file mode 100644 index 0000000000..fdd5404149 --- /dev/null +++ b/libopenage/curve/curve.cpp @@ -0,0 +1,10 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "curve.h" + +namespace openage { +namespace curve { + +// This file is intended to be empty + +}} // openage::curve diff --git a/libopenage/curve/curve.h b/libopenage/curve/curve.h new file mode 100644 index 0000000000..6fa0b779ca --- /dev/null +++ b/libopenage/curve/curve.h @@ -0,0 +1,19 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +namespace openage { +namespace curve { + +/** + * Defines the type that is used as time index. + * it has to implement all basic mathematically operations. + * + * pxd: + * ctypedef double curve_time_t + */ +typedef int64_t curve_time_t; + +}} // openage::curve diff --git a/libopenage/curve/datatypes.md b/libopenage/curve/datatypes.md new file mode 100644 index 0000000000..765ce74ff8 --- /dev/null +++ b/libopenage/curve/datatypes.md @@ -0,0 +1,120 @@ +TUBE DATATYPES +================= + +This document describes the datatypes that should be available within the tubes library. +They consists of simple, single dimensinal types and list types. + +This document is intended for brainstorming on needed datatypes. + +Simple Types +------------ + +Simple types only have one distinct value at a specific point in time. + +Discrete Interpolation +---------------------- + +"Step function" style values. These types adopt the new value exactly at the point in time changed. +They are useful for example for values like unit capacity, hitpoints, resource count, ... + +Linear Interpolation +-------------------- + +Linear connections between two points. These types have to overload the operators + and *, since these +are used to interpolate between t\_n and t\_n+1. These values change consistently over time, if t\_n+1 exists. +These are useful for example for unit position, building progress, ... + +Nyan Values +-------------- + +This container keeps track of nyan objects over time and their respective properties. + +Container Types +=============== + +Container types hold lists of items at any given time. They store a creation and a deletion timestamp for each item, and offer iteration logic to traverse the active elements in the container. + +Map Container +------------- + +The map container stores items based on an unique identifier mapped to an item. It keeps track of the existence of the item. No guaranteed order of items within this container is given (similar to std::unordered_map). +This container is useful for example for unit lists ... + +Set Container +---------------- + +The set container stores items, just as a normal array would. It keeps track of the existence of the items, but does not guarentee any particular ordering (similar to std::unordered_set). +This Container is useful for any non-indexed data structures, for example projectiles. + +Queue Container +--------------- + +The queue container represents a random access queue while keeping the ordering of the queue. +It is usually used for pushing in the back and popping at the front (FIFO-Stlye) but offers random access insertion and deletion as well. +This container is useful for example for action queues and buildung queues. + +TUBE SERIALIZATION +================== + +Serialization condenses data into change sets: + +Repeat the following blob: ++-----------------------------------------------------------+ +| ID (24Bit) | +| flags (delete, del_after, time, time2, data, add) (8Bit) | # In the first quadruple it is stored which data fields are set. +| if (flag & time) time1 | # In the second quadruple the usage of the data is stored +| if (flag & time2) time2 | # | time | time2 | data | UNUSED | delete | add | del_after | UNUSED | +| if (flag & data) keyframe: size(16) | data | ++-----------------------------------------------------------+ + +Meaning of Flags +---------------- + +== DELETE == + +After DELETE it is allowed to reuse the ID +When no Time is defined, then the deletion is "now", if TIME1 is defined, then the element will be deleted at this time. + +== ADD == + +Create a new element with the given ID. Add has to have at least TIME1 and DATA set. + +== DEL_AFTER == + +Delete all keyframes after TIME. + +== TIME1 == + +Set the Keyframe time or the creation time of an element + +== TIME2 == + +Set the Destruction time of a container element + +== DATA == + +The Keyframe data prefixed by data length + + + +Serialization of keyframes for different data types +---------------------------------------------------- + +Simple types: Binary Serialization of the data types, interpolation mode does not matter + +Containers +For Containers DELETE_AFTER is not supported. + +== Map == + +Store TIME2 as death time - if the element has a death time yet. +The ID of the object is submitted at creation as its own curve. + +== Set == + +This container is simple to store only times (birth (TIME1) and death (TIME2) of each unit) and only update the keyframe data when neccesary + +== Queue == + +Elements here have only one single time, so TIME2 is not used. +They can be created with ADD and removed with DELETE. diff --git a/libopenage/curve/demo/CMakeLists.txt b/libopenage/curve/demo/CMakeLists.txt new file mode 100644 index 0000000000..e46acf1aa5 --- /dev/null +++ b/libopenage/curve/demo/CMakeLists.txt @@ -0,0 +1,6 @@ +add_sources (libopenage + main.cpp + physics.cpp + gui.cpp + aicontroller.cpp +) diff --git a/libopenage/curve/demo/aicontroller.cpp b/libopenage/curve/demo/aicontroller.cpp new file mode 100644 index 0000000000..270fe03e03 --- /dev/null +++ b/libopenage/curve/demo/aicontroller.cpp @@ -0,0 +1,30 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "aicontroller.h" + +namespace openage { +namespace curvepong { + +std::vector &AIInput::getInputs( + const std::shared_ptr &player, + const std::shared_ptr &ball, + const curve::curve_time_t &now) { + this->event_cache.clear(); + + auto position = player->position->get(now); + + // Yes i know, there is /3 used - instead of the logical /2 - this is to + // create a small safety boundary of 1/3 for enhanced fancyness + + // Ball is below position + if (ball->position->get(now)[1] > position + player->size->get(now) / 3) { + event_cache.push_back(event(player->id(), event::DOWN)); + } else if (ball->position->get(now)[1] < position - player->size->get(now) / 3) { + // Ball is above position + event_cache.push_back(event(player->id(), event::UP)); + } + + return this->event_cache; +} + +}} // openage::curvepong diff --git a/libopenage/curve/demo/aicontroller.h b/libopenage/curve/demo/aicontroller.h new file mode 100644 index 0000000000..abcade24ec --- /dev/null +++ b/libopenage/curve/demo/aicontroller.h @@ -0,0 +1,24 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "config.h" +#include "gamestate.h" + +#include + +namespace openage { +namespace curvepong { + +class AIInput { +public: + std::vector &getInputs( + const std::shared_ptr &player, + const std::shared_ptr &ball, + const curve::curve_time_t &now); + +private: + std::vector event_cache; +}; + +}} // openage::curvepong diff --git a/libopenage/curve/demo/config.h b/libopenage/curve/demo/config.h new file mode 100644 index 0000000000..9e09c4a4e0 --- /dev/null +++ b/libopenage/curve/demo/config.h @@ -0,0 +1,17 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +// Define this to draw an ncurses based demo. +// Without the GUI flag, just a trace of event is printed out +#define GUI + +// If this is defined, player 1 can be played with the arrow keys. +// else the player is replaced by an AI. +//#define HUMAN + +// This can take the following values: +// 0: run in real time with real deltas +// 1: run very slow +// 2: run very fast +#define REALTIME 1 diff --git a/libopenage/curve/demo/gamestate.cpp b/libopenage/curve/demo/gamestate.cpp new file mode 100644 index 0000000000..c91b21ccb0 --- /dev/null +++ b/libopenage/curve/demo/gamestate.cpp @@ -0,0 +1,10 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "gamestate.h" + +namespace openage { +namespace curvepong { + +// This file is intentionally left empty + +}} // openage::curvepong diff --git a/libopenage/curve/demo/gamestate.h b/libopenage/curve/demo/gamestate.h new file mode 100644 index 0000000000..cb026d45e8 --- /dev/null +++ b/libopenage/curve/demo/gamestate.h @@ -0,0 +1,116 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "config.h" + +#include "../events/eventtarget.h" +#include "../continuous.h" +#include "../discrete.h" +#include "../../util/vector.h" + +#include +namespace openage { +namespace curvepong { + +struct event { + int player; + enum state_e { + UP, DOWN, START, IDLE, LOST + } state; + event(int id, state_e s) : player(id), state(s) {} + event() : player(0), state(IDLE) {} +}; + + +using namespace std::placeholders; +class PongPlayer : public openage::curve::EventTarget { +public: + PongPlayer(curve::EventManager *mgr, size_t id) : + EventTarget(mgr), + speed(std::make_shared>( + mgr, + (id << 4) + 1, + std::bind(&PongPlayer::child_changed, this, _1))), + position(std::make_shared>( + mgr, + (id << 4) + 2, + std::bind(&PongPlayer::child_changed, this, _1))), + lives(std::make_shared>( + mgr, + (id << 4) + 3, + std::bind(&PongPlayer::child_changed, this, _1))), + state(std::make_shared>( + mgr, + (id << 4) + 4, + std::bind(&PongPlayer::child_changed, this, _1))), + size(std::make_shared>( + mgr, + (id << 4) + 5, + std::bind(&PongPlayer::child_changed, this, _1))), + _id{id}, + y{0} {} + + std::shared_ptr> speed; + std::shared_ptr> position; + std::shared_ptr> lives; + std::shared_ptr> state; + std::shared_ptr> size; + size_t _id; + float y; + + size_t id() const override{ + return _id; + } +private: + void child_changed(const curve::curve_time_t &time) { + this->on_change(time); + } +}; + + +class PongBall : public openage::curve::EventTarget { +public: + PongBall(curve::EventManager *mgr,size_t id) : + EventTarget(mgr), + speed(std::make_shared>>( + mgr, + (id << 2) + 1, + std::bind(&PongBall::child_changed, this, _1))), + position(std::make_shared>>( + mgr, + (id << 2) + 2, + std::bind(&PongBall::child_changed, this, _1))), + _id{id} + {} + + std::shared_ptr>> speed; + std::shared_ptr>> position; + + size_t id() const override{ + return _id; + } +private: + void child_changed(const curve::curve_time_t &time) { + this->on_change(time); + } + size_t _id; +}; +}} // namespace openage::curvepong + + +namespace openage { +class State { +public: + State(curve::EventManager *mgr) : + p1(std::make_shared(mgr, 0)), + p2(std::make_shared(mgr, 1)), + ball(std::make_shared(mgr, 2)) {} + + std::shared_ptr p1; + std::shared_ptr p2; + std::shared_ptr ball; + util::Vector<2> resolution; +}; + +} // namespace openage diff --git a/libopenage/curve/demo/gui.cpp b/libopenage/curve/demo/gui.cpp new file mode 100644 index 0000000000..0e9afdf445 --- /dev/null +++ b/libopenage/curve/demo/gui.cpp @@ -0,0 +1,179 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "gui.h" + +#include +#include + +namespace openage { +namespace curvepong { + +std::vector &Gui::getInputs(const std::shared_ptr &player) { + input_cache.clear(); + event evnt; + evnt.player = player->id(); + evnt.state = event::IDLE; + timeout(0); + int c = getch(); + mvprintw(0,30, "IN: %i", c); + switch (c) { + case KEY_DOWN: + evnt.state = event::DOWN; + input_cache.push_back(evnt); + mvprintw(1, 1, "DOWN"); + break; + case KEY_UP: + evnt.state = event::UP; + input_cache.push_back(evnt); + mvprintw(1, 1, "UP"); + break; + case ' ': + evnt.state = event::START; + break; + case 27: // esc or alt + endwin(); + exit(0); + break; + case 114: //r + evnt.state = event::START; + input_cache.push_back(evnt); + break; + default: break; + } + + return input_cache; +} + + +enum { + COLOR_PLAYER1 = 1, + COLOR_PLAYER2 = 2, + COLOR_BALL = 3, + COLOR_DEBUG = 4, + + COLOR_0 = 5, + COLOR_1 = 6, + COLOR_2 = 7, + COLOR_3 = 8, + COLOR_4 = 9, +}; + + +Gui::Gui() { + initscr(); + start_color(); + init_pair(COLOR_PLAYER1, COLOR_BLUE, COLOR_BLUE); + init_pair(COLOR_PLAYER2, COLOR_RED, COLOR_RED); + init_pair(COLOR_BALL, COLOR_BLUE, COLOR_WHITE); + init_pair(COLOR_DEBUG, COLOR_WHITE, COLOR_BLACK); + init_pair(COLOR_0, COLOR_RED, COLOR_BLACK); + init_pair(COLOR_1, COLOR_GREEN, COLOR_BLACK); + + keypad(stdscr, true); + noecho(); + curs_set(0); + + int x, y; + getmaxyx(stdscr, y, x); + + attron(COLOR_PAIR(COLOR_DEBUG)); + + std::vector buffer{ + "oooooooooo ", + " 888 888 ooooooo ooooooo oooooooo8 ", + " 888oooo88 888 888 888 888 888 88o ", + " 888 888 888 888 888 888oo888o ", + "o888o 88ooo88 o888o o888o 88 888 ", + " 888ooo888", + }; + + size_t colwidth = 0; + for (const auto &c : buffer) { + colwidth = std::max(colwidth, strlen(c)); + } + int row = (y - buffer.size()) / 2;; + int col = (x - colwidth) / 2; + for (const auto &c : buffer) { + mvprintw(row++, col, c); + } + attroff(COLOR_PAIR(COLOR_DEBUG)); + + getch(); +} + + +void Gui::draw(std::shared_ptr &state, const curve::curve_time_t &now) { + // clear(); + // Print Score + attron(COLOR_PAIR(COLOR_DEBUG)); + getmaxyx(stdscr, state->resolution[1], state->resolution[0]); + state->resolution[1] -= 1; + attron(COLOR_PAIR(COLOR_DEBUG)); + mvprintw(2, + state->resolution[0] / 2 - 5, + "P1 %i | P2 %i", + state->p1->lives->get(now), + state->p2->lives->get(now)); + + mvvline(0, state->resolution[0] / 2, ACS_VLINE, state->resolution[1]); + mvprintw(0, 1, "NOW: %d", now); + mvprintw(1, 1, "SCR: %i | %i", state->resolution[0], state->resolution[1]); + mvprintw(2, + 1, + "P1: %f, %f, %i", + state->p1->position->get(now), + state->p1->y, + state->p1->state->get(now).state); + mvprintw(3, + 1, + "P2: %f, %f, %i", + state->p2->position->get(now), + state->p2->y, + state->p2->state->get(now).state); + for (int i = 0; i < 100; i += 10) { + mvprintw(4 + i / 10, + 1, + "BALL in %03i: %f | %f; SPEED: %f | %f", + i, + state->ball->position->get(now + i)[0], + state->ball->position->get(now + i)[1], + state->ball->speed->get(now + i)[0], + state->ball->speed->get(now + i)[1]); + } + mvprintw(state->resolution[1] - 1, 1, "Press ESC twice to Exit"); + attroff(COLOR_PAIR(COLOR_DEBUG)); + + attron(COLOR_PAIR(COLOR_PLAYER1)); + for (int i = -state->p1->size->get(now) / 2; i < state->p1->size->get(now) / 2; i++) { + mvprintw(state->p1->position->get(now) + i, state->p1->y, "|"); + } + attroff(COLOR_PAIR(COLOR_PLAYER1)); + + attron(COLOR_PAIR(COLOR_PLAYER2)); + for (int i = -state->p2->size->get(now) / 2; i < state->p2->size->get(now) / 2; i++) { + mvprintw(state->p2->position->get(now) + i, state->p2->y, "|"); + } + attroff(COLOR_PAIR(COLOR_PLAYER2)); + + attron(COLOR_PAIR(COLOR_1)); + for (int i = 1; i < 9999; ++i) { + draw_ball(state->ball->position->get(now + i), 'X'); + } + attron(COLOR_PAIR(COLOR_0)); + draw_ball(state->ball->position->get(now), 'M'); + /*attron(COLOR_PAIR(COLOR_BALL)); + mvprintw(state->ball->position->get(now)[1], + state->ball->position->get(now)[0], + "o"); + */ + attroff(COLOR_PAIR(COLOR_BALL)); + refresh(); + erase(); +} + + +void Gui::draw_ball(util::Vector<2> pos, char chr) { + mvprintw((int)(pos[1]), (int)(pos[0]), "%c", chr); + standend(); +} +}} // openage::curvepong diff --git a/libopenage/curve/demo/gui.h b/libopenage/curve/demo/gui.h new file mode 100644 index 0000000000..b5bda31620 --- /dev/null +++ b/libopenage/curve/demo/gui.h @@ -0,0 +1,26 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "config.h" +#include "gamestate.h" + +#include + +namespace openage { +namespace curvepong { + + +class Gui { + +public: + Gui(); + std::vector &getInputs(const std::shared_ptr &player); + void draw(std::shared_ptr &state, const curve::curve_time_t &now); + void draw_ball(util::Vector<2> ball, char chr); + +private: + std::vector input_cache; +}; + +}} // openage::curvepong diff --git a/libopenage/curve/demo/main.cpp b/libopenage/curve/demo/main.cpp new file mode 100644 index 0000000000..48a2924ed7 --- /dev/null +++ b/libopenage/curve/demo/main.cpp @@ -0,0 +1,116 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "config.h" +#include "aicontroller.h" +#include "gamestate.h" +#include "gui.h" +#include "physics.h" + +#include "../events/event.h" + +#include + +#include + +#include + +typedef std::chrono::high_resolution_clock Clock; + +namespace openage { +namespace curvepong { + +int demo() { + // Restart forever +#ifdef GUI + curvepong::Gui gui; +#endif +bool running = true; + srand(time(NULL)); + while (running) { + curve::EventManager events; + curve::curve_time_t now = 1; + curvepong::Physics phys; + curvepong::AIInput ai; + auto state = std::make_shared(&events); + curvepong::Physics::init(state, &events, now); + + state->p1->lives->set_drop(now, 3); + state->p1->size->set_drop(now, 4); + + state->p2->lives->set_drop(now, 3); + state->p2->size->set_drop(now, 4); +#ifdef GUI + gui.draw(state, now); // update gui related parameters +#else + state->resolution[0] = 100; + state->resolution[1] = 40; +#endif + + auto loop_start = Clock::now(); + now += 1; + { + std::vector start_event { event(0, event::START) }; + phys.processInput(state, state->p1, start_event, &events, now); + } + +#ifdef GUI + gui.draw(state, now); // initial drawing with corrected ball +#endif + while (state->p1->lives->get(now) > 0 && state->p2->lives->get(now) > 0) { + //We need to get the inputs to be able to kill the game +#ifdef HUMAN + phys.processInput(state, state->p1, gui.getInputs(state->p1), &events, now); +#else +#ifdef GUI + gui.getInputs(state->p1); +#endif + + phys.processInput( + state, state->p1, ai.getInputs(state->p1, state->ball, now), &events, now); +#endif + phys.processInput( + state, state->p2, ai.getInputs(state->p2, state->ball, now), &events, now); + + state->p1->y = 0; + state->p2->y = state->resolution[0] - 1; + + events.execute_until(now, state); +// phys.update(state, now); +#ifdef GUI + gui.draw(state, now); + + + int pos = 1; + mvprintw(pos++, state->resolution[0]/2 + 10, "Queue: "); + for (const auto & e : events.queue.ro_queue()) { + mvprintw(pos++, state->resolution[0]/2 + 10, + "%d: %s ", + e->time(), e->eventclass()->id().c_str()); + } + +#endif + +#if REALTIME == 0 + double dt = std::chrono::duration_cast( + (Clock::now() - loop_start)) + .count(); + if (dt < 12000) { + usleep(12000 - dt); + } + now += dt; +#elif REALTIME == 1 + now += 1; + usleep(20000); +#elif REALTIME == 2 + now += 4; +#else + #error no REALTIME plan set +#endif + loop_start = Clock::now(); + } + } + return 0; +} + + +}} // openage::curvepong diff --git a/libopenage/curve/demo/physics.cpp b/libopenage/curve/demo/physics.cpp new file mode 100644 index 0000000000..215f06cbd5 --- /dev/null +++ b/libopenage/curve/demo/physics.cpp @@ -0,0 +1,331 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "physics.h" + +#include "../../error/error.h" + +#include +#include + +#include + +namespace openage { +namespace curvepong { + +const float extrapolating_time = 100.0f; + +using namespace curve; + +class BallReflectWall : public EventClass { +public: + BallReflectWall () : EventClass("demo.ball.reflect_wall", EventClass::Type::ON_CHANGE) {} // FIXME: on_change as own class + + void setup(const std::shared_ptr &evnt, + const std::shared_ptr &state) override { + this->add_dependency(evnt, state->ball->position); // FIXME: add_trigger (and warn if not on_change) + this->add_dependency(evnt, state->ball->speed); + // TODO add dependency to size of area + // FIXME dependency to a full ball object + } + + // FIXME we REALLY need dependencies to objects i.e. Ball : public EventTarget() + // FIXME rename to invoke + // FIXME EventTarget for BALL!! + void call(curve::EventManager *, + const std::shared_ptr &target, + const std::shared_ptr &state, + const curve::curve_time_t &now, + const EventClass::param_map &/*param*/) override { + // TODO Do we really need to check for the time? + auto positioncurve = std::dynamic_pointer_cast>>(target); + auto speedcurve = state->ball->speed; + + // All the magic in the next lines + auto speed = speedcurve->get(now); + auto pos = positioncurve->get(now); + speed[1] *= -1.0; + state->ball->speed->set_drop(now, speed); + state->ball->position->set_drop(now, pos); + + if (speed[1] == 0) + return; + + double ty = 0; + if (speed[1] > 0) { + // fixme rename resolution to display_boundary + ty = (state->resolution[1] - pos[1]) / speed[1]; + } else if (speed[1] < 0) { + ty = pos[1] / -speed[1]; + } else { + } + state->ball->position->set_drop(now+ty, pos + speed * ty); + } + + // FIXME rename to predict_invoke_time + curve_time_t recalculate_time(const std::shared_ptr &target, + const std::shared_ptr &state, + const curve_time_t &now) override { + auto positioncurve = std::dynamic_pointer_cast>>(target); + auto speed = state->ball->speed->get(now); + auto pos = positioncurve->get(now); + + if (speed[1] == 0) { + return std::numeric_limits::max(); + } + double ty = 0; + if (speed[1] > 0) { + ty = (state->resolution[1] - pos[1]) / speed[1]; + } else if (speed[1] < 0) { + ty = pos[1] / -speed[1]; + } else { + } +#ifdef GUI + mvprintw(22, 40, "WALL TY %f NOW %f, NOWTY %f ", ty, now, now + ty); +#endif + return now + ty; + } +}; + +class BallReflectPanel : public curve::EventClass { +public: + BallReflectPanel () : curve::EventClass("demo.ball.reflect_panel", EventClass::Type::ON_CHANGE) {} + + void setup(const std::shared_ptr &target, + const std::shared_ptr &state) override { + add_dependency(target, state->ball->position); + add_dependency(target, state->ball->speed); + add_dependency(target, state->p1->position); + add_dependency(target, state->p2->position); + // TODO add dependency to size of area + // FIXME dependency to a full ball object + } + + // FIXME we REALLY need dependencies to objects + void call(curve::EventManager *mgr, + const std::shared_ptr & /*target*/, + const std::shared_ptr &state, + const curve::curve_time_t &now, + const EventClass::param_map &/*param*/) override { + + auto pos = state->ball->position->get(now); + auto speed = state->ball->speed->get(now); +#ifdef GUI + static int cnt = 0; + mvprintw(21, 22, "Panel hit [%i] ", ++cnt); +#else + std::cout << "\nPanel\n"; +#endif + + if (pos[0] <= 1 + && speed[0] < 0 + && (pos[1] < state->p1->position->get(now) - state->p1->size->get(now) / 2 + || pos[1] > state->p1->position->get(now) + state->p1->size->get(now) / 2)) { + // Ball missed the paddel of player 1 + auto l = state->p1->lives->get(now); + l--; + state-> + p1->lives->set_drop(now, l); + state->ball->position->set_drop(now, pos); + Physics::reset(state, mgr, now); + mvprintw(21, 18, "1"); + } else if (pos[0] >= state->resolution[0]-1 + && speed[0] > 0 + && (pos[1] < state->p2->position->get(now) - state->p2->size->get(now) / 2 + || pos[1] > state->p2->position->get(now) + state->p2->size->get(now) / 2)) { + // Ball missed the paddel of player 2 + auto l = state->p2->lives->get(now); + l --; + state->p2->lives->set_drop(now, l); + state->ball->position->set_drop(now, pos); + Physics::reset(state, mgr, now); + mvprintw(21, 18, "2"); + } else if (pos[0] >= state->resolution[0]- 1 || pos[0] <= 1) { + speed[0] *= -1; + state->ball->speed->set_drop(now, speed); + state->ball->position->set_drop(now, pos); + } + + float ty = 0; + if (speed[0] > 0) { + ty = (state->resolution[0] - pos[0]) / speed[0]; + } else if (speed[0] < 0) { + ty = pos[0] / -speed[0]; + } + auto hit_pos = pos + speed * ty; + hit_pos[0] = speed[0] > 0 ? state->resolution[0] : 0; + state->ball->position->set_drop(now+ty, hit_pos); + } + + curve_time_t recalculate_time(const std::shared_ptr &target, + const std::shared_ptr &state, + const curve_time_t &now) override { + auto positioncurve = std::dynamic_pointer_cast>>(target); + auto speed = state->ball->speed->get(now); + auto pos = positioncurve->get(now); + + if (speed[0] == 0) + return std::numeric_limits::max(); + float ty = 0; + if (speed[0] > 0) { + ty = (state->resolution[0] - pos[0]) / speed[0]; + } else if (speed[0] < 0) { + ty = pos[0] / -speed[0]; + } +#ifdef GUI + mvprintw(21, 40, "AT %f NEXT %f", now, now + ty); + mvprintw(21, 18, "2"); +#endif + auto hit_pos = pos + speed * ty; + hit_pos[0] = speed[0] > 0 ? state->resolution[0] : 0; + assert(hit_pos[0] >= 0); + return now + ty; + } +}; + + +class ResetGame : public curve::EventClass { +public: + ResetGame () : curve::EventClass("demo.reset", EventClass::Type::ONCE) {} + + void setup(const std::shared_ptr &target, const std::shared_ptr &state) override { + (void)target; + (void)state; + } + + void call(curve::EventManager *mgr, + const std::shared_ptr &target, + const std::shared_ptr &state, + const curve::curve_time_t &now, + const EventClass::param_map &/*param*/) override { + (void) mgr; + (void) target; + + // Check if the condition still applies + { + auto pos = state->ball->position->get(now); + if (pos[0] > 0 && pos[0] < state->resolution[0]) { + // the gamestate is still valid - there is no need to reset + return; + } + } + + state->ball->position->set_drop(now-0.1, state->ball->position->get(now)); + state->ball->position->set_drop(now, state->resolution * 0.5); + state->p1->position->set_drop(now, state->resolution[1] / 2); + state->p2->position->set_drop(now, state->resolution[1] / 2); + + float dirx = 1;//(rand() % 2 * 2) - 1; + float diry = 0.1;//(rand() % 2 * 2) - 1; + auto init_speed = + util::Vector<2>( + dirx * (0.001 + (rand() % 100) / 100.f), + diry * (0.001 + (rand() % 100) / 200.f)); + + state->ball->speed->set_drop(now, init_speed); + auto pos = state->ball->position->get(now); + static int cnt = 0; + mvprintw(20, 20, "Reset. Speed %f | %f POS %f | %f [%i]", + init_speed[0], + init_speed[1], + pos[0], + pos[1], + ++cnt); + + double ty = 0; + if (init_speed[1] > 0) { + ty = (state->resolution[1] - pos[1]) / init_speed[1]; + } else if (init_speed[1] < 0) { + ty = pos[1] / -init_speed[1]; + } else { + } + state->ball->position->set_drop(now+ty, pos + init_speed * ty); + } + + curve_time_t recalculate_time(const std::shared_ptr &target, + const std::shared_ptr &state, + const curve_time_t &old_time) override { + (void) target; + (void) state; + return old_time; + } +}; + +void Physics::init(std::shared_ptr &state, + curve::EventManager *mgr, + const curve::curve_time_t &now) { +#ifndef GUI + std::cout << "Init" << std::endl; +#endif + mgr->add_class(std::make_shared()); + mgr->add_class(std::make_shared()); + mgr->add_class(std::make_shared()); + + mgr->on("demo.ball.reflect_wall", state->ball->position, state, now); + mgr->on("demo.ball.reflect_panel", state->ball->position, state, now); + // FIXME once "reset": deregister + +// reset(state, mgr, now); +} + +void Physics::processInput(std::shared_ptr &state, + std::shared_ptr &player, + std::vector &events, + EventManager *mgr, + const curve_time_t &now) { + for (auto evnt : events) { + //Process only if the future has changed + if (player->state->get(now).state != evnt.state) { + player->state->set_drop(now, evnt); + + switch(evnt.state) { + case event::UP: + case event::DOWN: { + if (evnt.state == event::UP) { + player->speed->set_drop(now, -2); + } else if (evnt.state == event::DOWN) { + player->speed->set_drop(now, 2); + } + player->speed->set_drop(now + extrapolating_time, 0); + + float new_pos = player->position->get(now) + + (player->speed->get(now+extrapolating_time) + - player->speed->get(now) / 2 + player->speed->get(now)); + if (new_pos < 0) + new_pos = 0; + if (new_pos > state->resolution[1]) + new_pos = state->resolution[1]; + + player->position->set_drop(now+extrapolating_time, new_pos); + evnt.state = event::IDLE; + player->state->set_drop(now + extrapolating_time, evnt); +// predict_reflect_panel(state, queue, now); + break; + } + case event::IDLE: +// player->position->set_drop(now+extrapolating_time, +// player->position->get(now)); + break; + case event::START: + reset(state, mgr, now); + break; + + //if (player->state->get(now).state == event::LOST) { + // state->ball.position->set_drop(now, state.resolution * 0.5); + //} + //update_ball(state, now, init_recursion_limit); + //break; + default: + break; + } + } + } +} + + +void Physics::reset(const std::shared_ptr &state, + curve::EventManager *mgr, + const curve::curve_time_t &now) { + mgr->on("demo.reset", state->ball->position, state, now); +} + +}} // openage::curvepong diff --git a/libopenage/curve/demo/physics.h b/libopenage/curve/demo/physics.h new file mode 100644 index 0000000000..051fd5ee46 --- /dev/null +++ b/libopenage/curve/demo/physics.h @@ -0,0 +1,33 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "config.h" +#include "gamestate.h" + +#include "../curve.h" +#include "../events/eventmanager.h" + +#include + +namespace openage { +namespace curvepong { + +class Physics { +public: + static void init(std::shared_ptr &, + curve::EventManager *, + const curve::curve_time_t &); + + void processInput(std::shared_ptr &, + std::shared_ptr &, + std::vector &input, + curve::EventManager *, + const curve::curve_time_t &now); + + static void reset(const std::shared_ptr &, + curve::EventManager *mgr, + const curve::curve_time_t &); +}; + +}} // openage::curvepong diff --git a/libopenage/curve/discrete.cpp b/libopenage/curve/discrete.cpp new file mode 100644 index 0000000000..f0c58b4328 --- /dev/null +++ b/libopenage/curve/discrete.cpp @@ -0,0 +1,10 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "discrete.h" + +namespace openage { +namespace curve { + +// This file is intended to be empty + +}} // openage::curve diff --git a/libopenage/curve/discrete.h b/libopenage/curve/discrete.h new file mode 100644 index 0000000000..a4615578c8 --- /dev/null +++ b/libopenage/curve/discrete.h @@ -0,0 +1,36 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "internal/value_container.h" + +namespace openage { +namespace curve { + +/** + * Does not interpolate between values. The template type does only need to + * implement `operator=` and copy ctor. + */ +template +class Discrete : public ValueContainer<_T> { + static_assert(std::is_copy_assignable<_T>::value, + "Template type is not copy assignable"); + static_assert(std::is_copy_constructible<_T>::value, + "Template type is not copy constructible"); +public: + using ValueContainer<_T>::ValueContainer; + + /** + * Does not interpolate anything, just gives the raw value of the keyframe + */ + _T get(const curve_time_t &) const override; +}; + +template +_T Discrete<_T>::get(const curve_time_t &time) const { + auto e = this->container.last(time, this->last_element); + this->last_element = e; // TODO if Cacheing? + return e->value; +} + +}} // openage::curve diff --git a/libopenage/curve/eventqueue.md b/libopenage/curve/eventqueue.md new file mode 100644 index 0000000000..9a44c5a735 --- /dev/null +++ b/libopenage/curve/eventqueue.md @@ -0,0 +1,115 @@ +Event Queue Magic +===================== + +The Event Queue consists of Events and their Context: + +``` +struct Event { + +/// Unique Identificator for exactly this event. +int32_t event_id; + +/// Time this event should trigger +curve_time_ time_triggers; + +/// Class of the event +EventClass eventclass + +/// Curve Object this Event relates to +CurveObject context + +std::function &trgt, + const std::shared_ptr &eventclass, + const EventClass::param_map ¶ms) : + params(params), + _target{trgt}, + _eventclass{eventclass}, + myhash{util::hash_combine(std::hash()(trgt->id()), + std::hash()(eventclass->id())) } +{} + +}} // namespace openage::curve diff --git a/libopenage/curve/events/event.h b/libopenage/curve/events/event.h new file mode 100644 index 0000000000..725877a457 --- /dev/null +++ b/libopenage/curve/events/event.h @@ -0,0 +1,54 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "eventclass.h" + +#include "../curve.h" + +#include + +namespace openage { +namespace curve { + +class EventQueue; +class EventTarget; + +/** + * The actual one event that may be called - it is used to manage the event itself. + * It does not need to be stored + */ +class Event { + friend EventQueue; + friend EventManager; +public: + + Event(const std::shared_ptr &trgt, + const std::shared_ptr &eventclass, + const EventClass::param_map ¶ms); + + std::weak_ptr &target() { return _target; } + std::shared_ptr &eventclass() {return _eventclass; } + + /** + * Reschedule will call the recalculate_time method to initiate a reschedule for the event + * it uses the reference_time as base for its calculation + */ + void reschedule(const curve_time_t reference_time); + + size_t hash() const { return myhash; } + curve_time_t time() const { return _time; } + void time(const curve_time_t &t) { _time = t; } + + curve_time_t last_triggered = 0; +private: + EventClass::param_map params; + + std::weak_ptr _target; + std::shared_ptr _eventclass; + size_t myhash; + curve_time_t _time; +}; + + +}} // namespace openage::curve diff --git a/libopenage/curve/events/eventclass.cpp b/libopenage/curve/events/eventclass.cpp new file mode 100644 index 0000000000..d9c57faf23 --- /dev/null +++ b/libopenage/curve/events/eventclass.cpp @@ -0,0 +1,34 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "eventclass.h" + +#include "event.h" +#include "eventtarget.h" + +#include "../../log/log.h" + +namespace openage { +namespace curve { + +void EventClass::add_dependency (const std::shared_ptr &dependency, + const std::shared_ptr &element) { + // ON_EXECUTE and ON_KEYFRAME do not listen on changes, so they do not have + // any dependents. + if (type != Type::ON_EXECUTE) { + log::log(DBG << "Adding change listener for: " << dependency->eventclass()->id() + << " to " << element->id()); + element->add_dependent(dependency); + } +} + + +EventClass::EventClass(const std::string &name, EventClass::Type type) : + type(type), + _id{name} {} + + +const std::string &EventClass::id() { + return _id; +} + +}} // namespace openage::curve diff --git a/libopenage/curve/events/eventclass.h b/libopenage/curve/events/eventclass.h new file mode 100644 index 0000000000..8272074a71 --- /dev/null +++ b/libopenage/curve/events/eventclass.h @@ -0,0 +1,167 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../curve.h" + +#include +#include +#include +#include + +namespace openage { +class State; +namespace curve { + +class Event; +class EventManager; +class EventTarget; + +/** + * A eventclass has to be implemented for every type of event that exists. + * + * There shall be a genric on_X class, with a trigger_X interface for generic + * events; + */ +class EventClass { +public: + enum class Type { + /// Will be triggered at the time of the event. + /// "at" is the time, when the new value comes into action. + /// We want to be called at this time. + ON_CHANGE, + + /// Will be triggered immediately, when something changes. + /// "at" is the time when something was changed. + /// Behaves exactly like ON_CHANGE, if the ON_CHANGE is in the current + /// execution frame. + ON_CHANGE_IMMEDIATELY, + + /// Will be triggered on keyframe transition, when a keyframe is not + /// relevant for the "now" execution anymore. "at" is the time of the + /// keyframe + ON_KEYFRAME, + + /// Will be triggered unconditionally at the set time, "at" is the + /// time that was set as return of recalculate_time. This event will + /// be issued again until recalculate_time returns +max(). To execute + /// Something only once (i.E. triggered somewhere from the logic and + /// not based on time, use ONCE + + ON_EXECUTE, + + /// Will be triggered only once, but until it is triggered the time, + /// when this should happen can be recalculated again and again using + /// the recalculate_time method. + ONCE + }; + + class param_map { + public: + using any_t = std::experimental::fundamentals_v1::any; + using map_t = std::map; + + param_map(std::initializer_list l) : map(l) {} + param_map(const map_t& map) : map{std::move(map)} {} + + // Returns the value, if it exists and is the right type. + // dflt if not. + template + _T get(const std::string &key, const _T& dflt = _T()) const { + auto it = this->map.find(key); + if (it != this->map.end() && this->check_type<_T>(it)) { + //return std::any_cast<_T>(it->second); + return std::experimental::fundamentals_v1::any_cast<_T>(it->second); + + } else { + return dflt; + } + } + + bool contains(const std::string &key) const { + return map.find(key) != map.end(); + } + + // Check if the type is correct + template + bool check_type(const std::string &key) const { + auto it = map.find(key); + if (it != map.end()) { + return check_type<_T>(it); + } + return false; + } + + private: + template + bool check_type(const map_t::const_iterator &it) const { + return it->second.type() == typeid(_T); + } + + private: + const map_t map; + + }; + + /** + * Constructor to be constructed with the unique identifier + */ + EventClass(const std::string &name, Type); + + /** + * The Type the event class was constructed as. + */ + const Type type; + + /** + * Return a unique Identifier + */ + const std::string &id(); + + /** + * To be used in the setup functionality + */ + void add_dependency (const std::shared_ptr &event, + const std::shared_ptr &dependency); + + /** + * The job of the setup function is to add all dependencies with other event + * targets found in state. + */ + virtual void setup(const std::shared_ptr &event, + const std::shared_ptr &state) = 0; + + /** + * will be called at the time, that was recalculated. + * Here all the stuff happens. + */ + virtual void call(EventManager *, + const std::shared_ptr &target, + const std::shared_ptr &state, + const curve_time_t &t, + const param_map ¶ms) = 0; + + /** + * will be called to recalculate, when the event shall happen. + * The Calculation is triggered whenever one of the set up dependencies was + * changed. + * + * \param target the target this was defined on + * \param state the state this shall work on + * \param at the time when the change happened, from there on it shall be + * calculated onwards + * + * If the event is not relevant anymore at shall be returned as negative ::max(); + * If the time is lower than the previous time, then it may happen, that the + * dependencies are not perfectly resolved anymore (if other events have + * already been calculated before that + */ + virtual curve_time_t recalculate_time(const std::shared_ptr &target, + const std::shared_ptr &state, + const curve::curve_time_t &at)= 0; + +private: + std::string _id; +}; + +}} // namespace openage::curve diff --git a/libopenage/curve/events/eventmanager.cpp b/libopenage/curve/events/eventmanager.cpp new file mode 100644 index 0000000000..ac422bb61f --- /dev/null +++ b/libopenage/curve/events/eventmanager.cpp @@ -0,0 +1,148 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "eventmanager.h" + +#include "event.h" +#include "eventclass.h" +#include "eventqueue.h" +#include "eventtarget.h" + +#include "../../error/error.h" +#include "../../log/log.h" + +namespace openage { +namespace curve { + +void EventManager::changed(const std::shared_ptr &event, const curve_time_t &changed_at) { + log::log(DBG << "Change: " << event->eventclass()->id() << " at " << changed_at); + queue.add_change(event, changed_at); +} + + +void EventManager::add_class(const std::shared_ptr &cls) { + classstore.insert(std::make_pair(cls->id(), cls)); +} + + +std::weak_ptr EventManager::on(const std::string &name, + const std::shared_ptr &target, + const std::shared_ptr &state, + const curve_time_t &reference_time, + const EventClass::param_map ¶ms) { + auto it = classstore.find(name); + if (it == classstore.end()) { + // TODO FAIL + throw Error(MSG(err) << "Trying to subscribe to eventclass " << name << ", which does not exist."); + } + return queue.add(target, it->second, state, reference_time, params); +} + + +std::weak_ptr EventManager::on(const std::shared_ptr &eventclass, + const std::shared_ptr &target, + const std::shared_ptr &state, + const curve_time_t &reference_time, + const EventClass::param_map ¶ms) { + auto it = classstore.find(eventclass->id()); + if (it == classstore.end()) { + auto res = classstore.insert(std::make_pair(eventclass->id(), eventclass)); + if (res.second) { + it = res.first; + } else { + return std::weak_ptr(); + } + } + + return queue.add(target, it->second, state, reference_time, params); +} + + +void EventManager::execute_until(const curve_time_t &time, + std::shared_ptr<::openage::State> &state) { + int cnt = 0; + do { + update_changes(state); + cnt = execute_events(time, state); + log::log(DBG << "Executed " << cnt << " events"); + } while (cnt != 0); + // Swap in the end of the execution, else we might skip changes that happen + // in the main loop for one frame - which is bad btw. + std::swap(queue.changes, queue.future_changes); + log::log(DBG << "Cycle done"); +} + + +int EventManager::execute_events(const curve_time_t &time_until, + const std::shared_ptr &state) { + log::log(DBG << "Pending Events: "); + for (const auto &e : queue.queue) { + log::log(DBG << "\tEVENT: T:" << e->time() << ": " << e->eventclass()->id()); + } + auto it = queue.queue.begin(); + int cnt = 0; + while ((it = queue.queue.begin()) != queue.queue.end() && (*it)->time() < time_until) { + auto tmp = *it; + queue.queue.pop_front(); + auto target = tmp->target().lock(); + if (target) { + this->active_event = tmp; + + log::log(DBG << "Calling Event \"" << tmp->eventclass()->id() + << "\" on Element \"" << target->id() + << "\" at time " << tmp->time()); + + tmp->eventclass()->call(this, target, state, tmp->time(), tmp->params); + active_event = nullptr; + cnt ++; + + if (tmp->eventclass()->type == EventClass::Type::ON_EXECUTE) { + auto new_time = tmp->eventclass()->recalculate_time(target, state, tmp->time()); + if (new_time != -std::numeric_limits::max()) { + tmp->time(new_time); + queue.readd(tmp); + } + } + } else { + // The element was already removed from the queue, so we can safely + // kill it by ignoring it. + } + } + return cnt; +} + + +void EventManager::update_changes(const std::shared_ptr &state) { + // The second magic happens here + log::log(DBG << "Updating " << queue.changes->size() << " changes"); + for (const auto &change : *queue.changes) { + auto evnt = change.evnt.lock(); + if (evnt) { + switch(evnt->eventclass()->type) { + case EventClass::Type::ONCE: + case EventClass::Type::ON_CHANGE: { + auto target = evnt->target().lock(); + // TODO what happens when the target is degraded? + if (target) { + curve_time_t new_time = evnt->eventclass() + ->recalculate_time(target, state, change.time); + log::log(DBG << "Recalculating Event " << evnt->eventclass()->id() << " on Element " << target->id() << " at time " << change.time << ", new time: " << new_time); + if (new_time != -std::numeric_limits::max()) { + evnt->time(new_time); + queue.update(evnt); + } + } + } break; + case EventClass::Type::ON_KEYFRAME: + case EventClass::Type::ON_CHANGE_IMMEDIATELY: + evnt->time(change.time); + queue.update(evnt); + break; + case EventClass::Type::ON_EXECUTE: + break; + } + } + } + queue.changes->clear(); +} + +}} // namespace openage::curve diff --git a/libopenage/curve/events/eventmanager.h b/libopenage/curve/events/eventmanager.h new file mode 100644 index 0000000000..0e8d480405 --- /dev/null +++ b/libopenage/curve/events/eventmanager.h @@ -0,0 +1,115 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "eventqueue.h" + +#include "../curve.h" + +#include +#include +#include +#include + +// The demo wants to display details +namespace openage { +class State; +namespace curvepong { +int demo(); +} +} + + +namespace openage { +class State; +namespace curve { + +class EventTarget; +class Event; + +class EventFilter { +public: + EventFilter(std::function &)> &filter) : + _filter(filter) {} + + bool apply(const std::shared_ptr target) { + return _filter(target); + } +private: + std::function&)> _filter; +}; + +/** + * The core class to manage event class and targets. + */ +class EventManager { + friend int openage::curvepong::demo(); +public: + void add_class(const std::shared_ptr &cls); + + std::weak_ptr on(const std::string &name, + const std::shared_ptr &target, + const std::shared_ptr &state, + const curve_time_t &reference_time, + const EventClass::param_map ¶ms = EventClass::param_map({})); + /** + * This will generate a new randonmly named eventclass for this specific element + */ + std::weak_ptr on(const std::shared_ptr &eventclass, + const std::shared_ptr &target, + const std::shared_ptr &state, + const curve_time_t &reference_time, + const EventClass::param_map ¶ms = EventClass::param_map({})); + + void onfilter(const std::shared_ptr &eventclass, const EventFilter &); + + template + void onfilter(const EventFilter &filter) { + onfilter(std::make_shared(), filter); + } + + void refilter(const std::shared_ptr &); + void refilter(const std::shared_ptr &); + + void register_object(const std::shared_ptr &); + + /** + * Submit a change in a dependent value to an event. Insert it into the + * changes queue and evaluate it accordingly + */ + void changed(const std::shared_ptr &event, + const curve_time_t &changed_at); + + /** + * Execute all events that are registered until a certain point in time. + */ + void execute_until(const curve_time_t &time, std::shared_ptr<::openage::State> &state); + +private: + /** + * Execute the events + * + * \return number of events processed + */ + int execute_events(const curve_time_t &time, + const std::shared_ptr &state); + + /** + * Call all the time change functions. This is constant on the state! + */ + void update_changes(const std::shared_ptr &state); + + /// Here we do the bookkeeping of registered classes. + std::map> classstore; + + /** + * Here we store all running filters that shall be applied whenever a new + * obejct is added to our objectstore + */ + std::list filters; + + EventQueue queue; + std::shared_ptr active_event; +}; + +}} // namespace openage::curve diff --git a/libopenage/curve/events/eventqueue.cpp b/libopenage/curve/events/eventqueue.cpp new file mode 100644 index 0000000000..ac9b7ea952 --- /dev/null +++ b/libopenage/curve/events/eventqueue.cpp @@ -0,0 +1,231 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "eventqueue.h" + +#include "event.h" +#include "eventclass.h" +#include "eventtarget.h" + +#include "../../error/error.h" +#include "../../log/log.h" + +namespace openage { +namespace curve { + +std::weak_ptr EventQueue::add(const std::shared_ptr &trgt, + const std::shared_ptr &cls, + const std::shared_ptr &state, + const curve_time_t &reference_time, + const EventClass::param_map ¶ms) { + auto sp = std::make_shared(trgt, cls, params); + + cls->setup(sp, state); + switch(cls->type) { + case EventClass::Type::ON_CHANGE: + case EventClass::Type::ON_EXECUTE: + case EventClass::Type::ONCE: + sp->time(sp->eventclass()->recalculate_time(trgt, state, reference_time)); + if (sp->time() == -std::numeric_limits::max()) { + return std::weak_ptr(); + } + break; + case EventClass::Type::ON_CHANGE_IMMEDIATELY: + case EventClass::Type::ON_KEYFRAME: + sp->_time = reference_time; + break; + } + select_queue(sp->eventclass()->type).emplace_back(sp); + + this->check_in(sp); + + return sp; +} + + +EventQueue::EventQueue() : + changes(&changeset_A), future_changes(&changeset_B) {} + + +void EventQueue::check_in(const std::shared_ptr &evnt) { + switch(evnt->eventclass()->type) { + case EventClass::Type::ON_CHANGE: + case EventClass::Type::ON_CHANGE_IMMEDIATELY: + // TODO Set up these fellows + break; + case EventClass::Type::ON_KEYFRAME: + // TODO set up this fellow + break; + case EventClass::Type::ON_EXECUTE: + // Add this to the event queue, although it is already in the other queue + queue.emplace_back(evnt); + break; + case EventClass::Type::ONCE: + // Has already been added to the queue, so nothing to do here + break; + } +} + + +void EventQueue::add_change(const std::shared_ptr &event, + const curve_time_t &changed_at) { + + auto it = changes->find(OnChangeElement(event, changed_at)); + + // Has the event been triggered in this round? + if (event->last_triggered < changed_at) { + // Is the change already in the queue? + if (it != changes->end()) { + // Is the new change dated _before_ the old one? + if (it->time > changed_at) { + // Save the element + OnChangeElement e = *it; + e.time = changed_at; + // delete it from the container and readd it + it = changes->erase(it); + it = changes->insert(it, e); + log::log(DBG << "adjusting time in change queue"); + } else { + // this change is to be ignored + log::log(DBG << "skipping change " << event->eventclass()->id() + << " at " << changed_at + << " because there was already a better one at " << it->time); + } + } else { + // the change was not in the to be changed list + changes->emplace(event, changed_at); + log::log(DBG << "inserting change " << event->eventclass()->id() << " at " << changed_at); + } + } else { + // the event has been triggered in this round already, so skip it this time + future_changes->emplace(event, changed_at); + log::log(DBG << "putting change into the future: " << event->eventclass()->id() << " at " << changed_at); + } + event->last_triggered = changed_at; +} + + +void EventQueue::remove(const std::shared_ptr &evnt) { + auto &queue = select_queue(evnt->eventclass()->type); + auto it = std::find(std::begin(queue), std::end(queue), evnt); + + if (it != queue.end()) { + queue.erase(it); + } +} + +std::deque> &EventQueue::select_queue(EventClass::Type type) { + switch(type) { + case EventClass::Type::ON_CHANGE: + return on_change; + break; + case EventClass::Type::ON_CHANGE_IMMEDIATELY: + return on_change_immediately; + break; + case EventClass::Type::ON_KEYFRAME: + return on_keyframe; + break; + case EventClass::Type::ON_EXECUTE: + return on_execute; + break; + default: + case EventClass::Type::ONCE: + return queue; + break; + } +} + + +void EventQueue::update(const std::shared_ptr &evnt) { + decltype(queue)::iterator it = std::find(std::begin(queue), std::end(queue), evnt); + if (it != queue.end()) { // we only want to update the pending queue + int dir = 0; + { + auto nxt = std::next(it); + if (nxt != std::end(queue) && (*nxt)->time() < (*it)->time()) { + dir --; + } + auto prv = std::prev(it); + if (it != std::begin(queue) && (*prv)->time() > (*it)->time()) { + dir ++; + } + // dir is now one of these values: + // 0: the element is perfectly placed + // +: the elements time is too low for its position, move forward + // -: the elements time is too high for its position, move backwards + } + if (dir == 0) { + // do nothing + } else if (dir > 0) { + auto e = *it; + it = queue.erase(it); + while (it != queue.begin() + && evnt->time() < (*std::prev(it))->time()) { + it--; + } + queue.insert(it, e); + } else if (dir < 0) { + auto e = *it; + it = queue.erase(it); + while (it != queue.end() + && evnt->time() > (*std::prev(it))->time()) { + it++; + } + queue.insert(it, e); + } + log::log(DBG << "Readded Element: " << evnt->eventclass()->id() << " at " << evnt->time()); + } else { + it = std::find_if( + std::begin(queue), + std::end(queue), + [&evnt] (const std::shared_ptr &e) { + return e->time() > evnt->time(); + }); + queue.insert(it, evnt); + log::log(DBG << "Inserted Element: " << evnt->eventclass()->id() << " at " << evnt->time()); + } +} + + +void EventQueue::readd(const std::shared_ptr &evnt) { + auto &container = select_queue(evnt->eventclass()->type); + + auto it = std::find_if( + std::begin(container), + std::end(container), + [&evnt](const std::shared_ptr &element) { + return element->time() < evnt->time(); + }); + container.insert(it, evnt); + + check_in(evnt); +} + + +EventQueue::OnChangeElement::OnChangeElement(const std::shared_ptr &evnt, + const curve_time_t &time) : + time{time}, + evnt{evnt}, + hash{evnt->hash()} {} + + +size_t EventQueue::OnChangeElement::Equal::operator()(const OnChangeElement& left, + const OnChangeElement& right) const { + auto left_evnt = left.evnt.lock(); + auto right_evnt = right.evnt.lock(); + if (left_evnt && right_evnt) { + if (left_evnt->eventclass()->id() == right_evnt->eventclass()->id()) { + return true; + } + } else { + return false; + } + + auto left_trgt = left_evnt->target().lock(); + auto right_trgt = right_evnt->target().lock(); + + return left_trgt && right_trgt && + left_trgt->id() == right_trgt->id(); +} + + +}} // namespace openage::curve diff --git a/libopenage/curve/events/eventqueue.h b/libopenage/curve/events/eventqueue.h new file mode 100644 index 0000000000..b3eb9ee240 --- /dev/null +++ b/libopenage/curve/events/eventqueue.h @@ -0,0 +1,125 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +// for eventclass::types +#include "eventclass.h" + +#include "../curve.h" + +#include "../../util/hash.h" + +#include +#include +#include + +namespace openage { +class State; +namespace curve { + +class Event; +class EventManager; +class EventTarget; + +/** + * The core event class for execution and execution dependencies. + */ +class EventQueue { + friend class EventManager; +public: + EventQueue(); + + /** + * Add an event for a specified target + * + * A target is every single unit in the game word - so best add these events + * in the constructor of the game objects. + */ + std::weak_ptr add(const std::shared_ptr &eventtarget, + const std::shared_ptr &eventclass, + const std::shared_ptr &state, + const curve_time_t &reference_time, + const EventClass::param_map ¶ms); + + void remove(const std::shared_ptr &evnt); + + /** + * The event may be already part of the execution queue, + * so update it, if it shall be executed in the future. + */ + void update(const std::shared_ptr &); + + /** + * The event was just removed. + */ + void readd(const std::shared_ptr &); + + /** + * An event target has changed, and the event shall be retriggered + */ + void add_change(const std::shared_ptr &event, const curve_time_t &changed_at); + + /** + * Get an event identified by EventTarget and EventClass, or a shared_ptr if not found + */ + const std::shared_ptr &get_evnt(const std::shared_ptr &, + const std::shared_ptr &); + + /** + * get an accessor to the running queue for state ouput purpose + */ + const std::deque> &ro_queue() const { return queue; } + +private: + void check_in(const std::shared_ptr &evnt); + + class OnChangeElement { + public: + OnChangeElement(const std::shared_ptr &evnt, + const curve_time_t &time); + + curve_time_t time; + std::weak_ptr evnt; + const size_t hash; + + class Hasher : public std::unary_function { + public: + size_t operator()(const OnChangeElement& e) const { + return e.hash; + } + }; + + class Equal : public std::unary_function { + public: + size_t operator()(const OnChangeElement& left, + const OnChangeElement& right) const; + }; + }; + + using change_set = std::unordered_set; + // Implement double buffering around changesets, that we do not run into deadlocks + change_set *changes; + change_set *future_changes; + + change_set changeset_A; + change_set changeset_B; + + std::deque> &select_queue(EventClass::Type type); + + std::deque> on_change; // will trigger itself and add to the queue + std::deque> on_change_immediately; // will trigger itself and at to the queue + std::deque> on_keyframe; // will trigger itself + // TODO is this required? + std::deque> on_execute; // whenever one of these is executed, it shall be reinserted + // Type::ONCE is only inserted into the queue + + + std::deque> queue; + + + std::shared_ptr null_evnt; +}; + +}} // namespace openage::curve diff --git a/libopenage/curve/events/events.cpp b/libopenage/curve/events/events.cpp new file mode 100644 index 0000000000..5e7161f174 --- /dev/null +++ b/libopenage/curve/events/events.cpp @@ -0,0 +1,17 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "events2.h" + +#include "../log/log.h" +#include "../error/error.h" + +#include + +namespace openage { +namespace curve { + + + + + +}} // namespace openage::curve diff --git a/libopenage/curve/events/events.h b/libopenage/curve/events/events.h new file mode 100644 index 0000000000..fedae6e178 --- /dev/null +++ b/libopenage/curve/events/events.h @@ -0,0 +1,34 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../curve.h" + +#include "../../util/hash.h" + +#include +#include +#include +#include +#include + +// Forward declarations +namespace openage { +class State; +namespace curvepong { +int demo(); +} +} + +namespace openage { +namespace curve { + +class Event; +class EventQueue; +class EventClass; +class EventManager; + + + + +}} // openage::curve diff --git a/libopenage/curve/events/eventtarget.cpp b/libopenage/curve/events/eventtarget.cpp new file mode 100644 index 0000000000..6e6608eb2d --- /dev/null +++ b/libopenage/curve/events/eventtarget.cpp @@ -0,0 +1,75 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "eventtarget.h" + +#include "event.h" +#include "eventclass.h" +#include "eventmanager.h" + +#include "../../error/error.h" +#include "../../log/log.h" + +namespace openage { +namespace curve { + +void EventTarget::on_change(const curve_time_t &time) { + log::log(DBG << "Change happened on " << this->id() << " at " << time); + if (parent_notifier != nullptr) { + parent_notifier(time); + } + // Iterator advancement is done inside the loop - it is an maybe-erase construct + for (auto it = dependents.begin(); it != dependents.end(); ) { + auto sp = it->lock(); + if (sp) { + switch (sp->eventclass()->type) { + case EventClass::Type::ON_CHANGE_IMMEDIATELY: + case EventClass::Type::ON_CHANGE: + // Change events will be retriggered + manager->changed(sp, time); + ++it; + break; + case EventClass::Type::ONCE: + // Has it been triggered? If so - forget it. + if (sp->last_triggered > 0) { + it = dependents.erase(it); + } else { + manager->changed(sp, time); + ++it; + } + break; + case EventClass::Type::ON_KEYFRAME: + case EventClass::Type::ON_EXECUTE: + // Ignore changes for execute events + ++it; + break; + } + } else { + // The dependent is no more, so we can safely forget him + it = dependents.erase(it); + } + } +} + + +void EventTarget::on_pass_keyframe(const curve_time_t &last_valid_time) { + for (auto it = dependents.begin(); it != dependents.end(); ) { + auto sp = it->lock(); + if (sp) { + if (sp->eventclass()->type == EventClass::Type::ON_KEYFRAME) { + log::log(DBG << "Encountered keyframe " << sp->eventclass()->id() + << " at T:" << last_valid_time); + manager->changed(sp, last_valid_time); + } + ++it; + } else { + it = dependents.erase(it); + } + } +} + + +void EventTarget::add_dependent(const std::weak_ptr &event) { + this->dependents.emplace_back(event); +} + +}} // namespace openage::curve diff --git a/libopenage/curve/events/eventtarget.h b/libopenage/curve/events/eventtarget.h new file mode 100644 index 0000000000..41db00b367 --- /dev/null +++ b/libopenage/curve/events/eventtarget.h @@ -0,0 +1,56 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../curve.h" + +#include +#include +#include + +namespace openage { +namespace curve { + +class Event; +class EventManager; +class EventClass; + +/** + * Every Object in the gameworld that wants to be targeted by events or as + * dependency for events, has to implement this class. + */ +class EventTarget { + friend EventClass; +public: + /** Give a unique identifier for the unit */ + virtual size_t id() const = 0; + + using single_change_notifier = std::function; +protected: + /** + * For children to be able to initialize us. + * + * The notifier is used by hierarchical structures to be able to traverse a + * change up in the tree, this is necessary to make containers with event + * targets inside and listen to any changes on the full. + */ + EventTarget(EventManager *manager, single_change_notifier parent_notifier = nullptr) : + manager{manager}, + parent_notifier{parent_notifier} {} + + /** Whenever some data in the Target changed */ + void on_change(const curve_time_t &); + + /** when a keyframe in the underlying container was passed. */ + void on_pass_keyframe(const curve_time_t &last_valid_time); + + /** add a dependent class, that should be notified when on_change is called */ + void add_dependent(const std::weak_ptr &event); + +private: + EventManager *manager; + std::list> dependents; + single_change_notifier parent_notifier; +}; + +}} // namespace openage::curve diff --git a/libopenage/curve/internal/CMakeLists.txt b/libopenage/curve/internal/CMakeLists.txt new file mode 100644 index 0000000000..1683189a25 --- /dev/null +++ b/libopenage/curve/internal/CMakeLists.txt @@ -0,0 +1,8 @@ +add_sources(libopenage + keyframe_container.cpp + value_container.cpp +) + +pxdgen( + value_container.h +) diff --git a/libopenage/curve/internal/keyframe_container.cpp b/libopenage/curve/internal/keyframe_container.cpp new file mode 100644 index 0000000000..ee3da68353 --- /dev/null +++ b/libopenage/curve/internal/keyframe_container.cpp @@ -0,0 +1,8 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "keyframe_container.h" + +namespace openage { +namespace curve { + +}} // openage::curve diff --git a/libopenage/curve/internal/keyframe_container.h b/libopenage/curve/internal/keyframe_container.h new file mode 100644 index 0000000000..7fce03b396 --- /dev/null +++ b/libopenage/curve/internal/keyframe_container.h @@ -0,0 +1,250 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../curve.h" + +#include "../../error/error.h" + +#include +#include +#include +#include + +namespace openage { +namespace curve { + +/** + * A timely ordered list with several management functions + * + * This class manages different time-based management functions for list + * approach that lies underneath. It contains list to be accessed via a + * non-accurate timing functionality, this means, that for getting a value, not + * the exact timestamp has to be known, it will always return the one closest, + * less or equal to the requested one. + **/ +template +class KeyframeContainer { +public: + /** + * A element of the curvecontainer. This is especially used to keep track of + * the value-timing. + */ + class Keyframe { + public: + /** + * New default object at time -INF. + */ + Keyframe() {} + + /** + * New, default-constructed element at the given time + */ + Keyframe(const curve_time_t &time) : + time{time} {} + + /** + * New element fron time and value + */ + Keyframe(const curve_time_t &time, const _T &value) : + time{time}, + value{value} {} + + const curve_time_t time = std::numeric_limits::max(); + _T value = _T(); + }; + + /** + * The underlaying container type. + * + * The most important property of this container is the iterator validity on + * insert and remove. + */ + typedef std::list curvecontainer; + + /** + * The iterator type to access elements in the container + */ + typedef typename curvecontainer::const_iterator KeyframeIterator; + + /** default c'tor **/ + KeyframeContainer(); + ~KeyframeContainer(); + + /** + * Get the last element with e->time <= time, given a hint where to + * start the search. + */ + KeyframeIterator last(const curve_time_t &time, + const KeyframeIterator & hint) const; + + /** + * Get the last element with e->time <= time, without a hint where to start + * searching. + * + * The usage of this method is discouraged - except if there is absolutely + * no chance for you to have a hint (or the container is known to be nearly + * empty) + */ + KeyframeIterator last(const curve_time_t &time) const { + return this->last(time, this->container.begin()); + } + + /** + * Insert a new element without a hint. + * + * This function is not recommended for use, whenever possible, keep a hint + * to insert the data. + */ + KeyframeIterator insert(const Keyframe &value) { + return this->insert(value, this->container.begin()); + } + + /** + * Insert a new element. The hint shall give an approximate location, where + * the inserter will start to look for a insertion point. If a good hint is + * given, the runtime of this function will not be affected by the current + * history size. + */ + KeyframeIterator insert(const Keyframe &value, const KeyframeIterator &hint); + + /** + * Create and insert a new element without submitting a hint. + * The use of this function is discouraged, use it only, if your really do not + * have the possibility to get a hint + */ + KeyframeIterator insert(const curve_time_t &time, const _T&value) { + return this->insert(Keyframe(time, value), this->container.begin()); + } + + /** + * Create and insert a new element. The hint gives an approximate location. + */ + KeyframeIterator insert(const curve_time_t &time, const _T&value, const KeyframeIterator &hint) { + return this->insert(Keyframe(time, value), hint); + } + + /** + * Erase all elements that come after this last valid element. + */ + KeyframeIterator erase_after(KeyframeIterator last_valid); + + /** + * Erase a single element from the curve. + */ + KeyframeIterator erase(KeyframeIterator ); + + /** + * Obtain an iterator to the first value with the smallest timestamp. + */ + KeyframeIterator begin() const { + return container.begin(); + } + + /** + * Obtain an iterator to the position after the last value. + */ + KeyframeIterator end() const { + return container.end(); + } + + /** + * Debugging method to be used from gcc to understand bugs better. + */ + void __attribute__ ((noinline)) dump() { + for (auto e : container) { + std::cout << "Element: time: " << e.time << " v: " << e.value << std::endl; + } + } +private: + /** + * The data store. + */ + curvecontainer container; +}; + + +template +KeyframeContainer<_T>::KeyframeContainer() { + //Create a default element at -Inf, that can always be dereferenced - so there will by definition never be + //a element that cannot be dereferenced + this->container.push_back(Keyframe(-std::numeric_limits::max(), _T())); +} + +template +KeyframeContainer<_T>::~KeyframeContainer() { + // We rely on std::list to destroy all elements. +} + +/** + * Select the element that directly preceedes the given timestamp. + * + * Without a hint, start to iterate at the beginning of the buffer, and return + * the element last element before e->time > time. + * This method returns nullptr, if begin->time > time. + **/ +template +typename KeyframeContainer<_T>::KeyframeIterator KeyframeContainer<_T>::last(const curve_time_t &time, const KeyframeIterator &hint) const { + KeyframeIterator e = (hint == this->container.end()) ? this->container.begin() : hint; + + if (this->container.front().time > time) { + // This will never happen due to the container.front->time == -Inf magic! + throw Error(ERR << "rupture in spacetime detected, curve container is broken"); + } + + // Search in the queue + if (time > e->time) { // the searched element is supposed to be AFTER the hint + // perform the search via ->next + while (e != this->container.end() && time >= e->time) { + e++; + } + e--; + // e is now one of two options: + // 1. e == end: The last element of the queue was smaller than `time` + // 2. e != end: There was a element with `e->time` > `time` + + } else if (time < e->time) { + // the searched element is supposed to be BEFORE the hint + // perform the search via ->prev + while (e != this->container.begin() && time < e->time) { + e--; + } + // e is now one of two options: + // 1. e == begin: The time was before every element in the queue + // 2. e != begin: There was an element with `e->time` > `time` + + } else { + // perform <= search - and return e, whose time is == time. + } + + return e; +} + +/** + * Determine where to insert based on time, and insert + */ +template +typename KeyframeContainer<_T>::KeyframeIterator KeyframeContainer<_T>::insert(const KeyframeContainer<_T>::Keyframe &e, const KeyframeContainer<_T>::KeyframeIterator &hint) { + KeyframeIterator at = this->last(e.time, hint); + at ++; + return this->container.insert(at, e); +} + +/** + * Go from the end to the last_valid element, and call erase on all of them + */ +template +typename KeyframeContainer<_T>::KeyframeIterator KeyframeContainer<_T>::erase_after(KeyframeContainer<_T>::KeyframeIterator last_valid) { + //Delete from the end to (excluded) last_valid + return this->container.erase(++last_valid, container.end()); +} + +/** + * Delete the element from the list and call delete on it. + */ +template +typename KeyframeContainer<_T>::KeyframeIterator KeyframeContainer<_T>::erase(KeyframeContainer<_T>::KeyframeIterator e) { + return this->container.erase(e); +} + +}} // openage::curve diff --git a/libopenage/curve/internal/value_container.cpp b/libopenage/curve/internal/value_container.cpp new file mode 100644 index 0000000000..c1d39ae291 --- /dev/null +++ b/libopenage/curve/internal/value_container.cpp @@ -0,0 +1,8 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "value_container.h" + +namespace openage { +namespace curve { + +}} // openage::curve diff --git a/libopenage/curve/internal/value_container.h b/libopenage/curve/internal/value_container.h new file mode 100644 index 0000000000..83855150a9 --- /dev/null +++ b/libopenage/curve/internal/value_container.h @@ -0,0 +1,97 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../events/eventtarget.h" + +#include "keyframe_container.h" + +#include + +namespace openage { +namespace curve { + +/** + * pxd: + * + * cppclass ValueContainer(EventTarget): + * void set_drop(const curve_time_t &) except + + * void set_insert(const curve_time_t &) except + + * + */ +template +class ValueContainer : public EventTarget { +public: + ValueContainer(EventManager *mgr, + size_t id, + const EventTarget::single_change_notifier ¬ifier = nullptr) : + EventTarget(mgr, notifier), + _id{id}, + last_element{container.begin()} {} + + virtual _T get(const curve_time_t &t) const = 0; + + virtual _T operator ()(const curve_time_t &now) { + return get(now); + } + + virtual std::pair frame(const curve_time_t &) const; + virtual std::pair next_frame(const curve_time_t &) const; + + // Inserter mode + virtual void set_drop(const curve_time_t &at, const _T &value); + virtual void set_insert(const curve_time_t &at, const _T &value); + + virtual size_t id() const override { + return _id; + } +protected: + KeyframeContainer<_T> container; + const size_t _id; + mutable typename KeyframeContainer<_T>::KeyframeIterator last_element; +}; + + +template +void ValueContainer<_T>::set_drop(const curve_time_t &at, const _T &value) { + auto hint = this->container.last(at, this->last_element); + + // We want to remove a possible equal timed element from the container + // to do fabs(x-y) < min is only necessary when curve_time_t is floating point! + //if (std::abs(hint->time - at) < std::numeric_limits::min()) { + + if (hint->time == at) { + hint--; + } + + hint = this->container.erase_after(hint); + + container.insert(at, value, hint); + this->last_element = hint; + + this->on_change(at); +} + + +template +void ValueContainer<_T>::set_insert(const curve_time_t &at, const _T &value) { + this->container.insert(at, value, this->last_element); + this->on_change(at); +} + + +template +std::pair ValueContainer<_T>::frame(const curve_time_t &time) const { + auto e = this->container.last(time, this->container.end()); + return std::make_pair(e->time, e->value); +} + + +template +std::pair ValueContainer<_T>::next_frame(const curve_time_t &time) const { + auto e = this->container.last(time, this->container.end()); + e ++; + return std::make_pair(e->time, e->value); +} + +}} // openage::curve diff --git a/libopenage/curve/iterator.cpp b/libopenage/curve/iterator.cpp new file mode 100644 index 0000000000..5f0f97c901 --- /dev/null +++ b/libopenage/curve/iterator.cpp @@ -0,0 +1,10 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "iterator.h" + +namespace openage { +namespace curve { + +// This file is intended to be empty + +}} // namespace openage::curve diff --git a/libopenage/curve/iterator.h b/libopenage/curve/iterator.h new file mode 100644 index 0000000000..64f4f021a5 --- /dev/null +++ b/libopenage/curve/iterator.h @@ -0,0 +1,129 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "curve.h" + +#include + +namespace openage { +namespace curve { + +/** + * Default interface for curve containers + */ +template +class CurveIterator { +public: + /** + * access the value of the iterator + */ + virtual const val_t &value() const = 0; + + /** + * Check if the iterator is still valid + * (this breaks from the stl - in the best way) + */ + virtual bool valid() const = 0; + + /** + * The iterator needs a reference to the container + */ + explicit CurveIterator(const container_t *c): + _base{}, + container{c}, + from{-std::numeric_limits::max()}, + to{+std::numeric_limits::max()} {} + + /** Default copy c'tor */ + CurveIterator (const CurveIterator &) = default; + + /** Default assignment operator */ + CurveIterator &operator= ( + const CurveIterator &) = default; + + /** Dereference will call the virtual function */ + virtual const val_t &operator *() const { + return this->value(); + } + + /** Dereference will call the virutal function */ + virtual const val_t *operator ->() const { + return &this->value(); + } + + /** + * For equalness only the base iterator will be testet - not the timespans + * this is defined in. + */ + virtual bool operator ==(const CurveIterator &rhs) const { + return this->_base == rhs._base; + } + + /** + * For unequalness only the base iterator will be testet - not the timespans + * this is defined in. + */ + virtual bool operator !=(const CurveIterator &rhs) const { + return this->_base != rhs._base; + } + + /** + * Advance to the next valid element. + */ + virtual CurveIterator &operator ++() { + do { + ++(this->_base); + } while (this->container->end()._base != this->_base && !this->valid()); + return *this; + } + + /** + * Access the underlying + */ + const iterator_t &base() const { + return _base; + } + + /** + * Access the lower end value of the defined time frame + */ + const curve_time_t &_from() const { + return from; + } + + /** + * Access the higher end value of the defined time frame + */ + const curve_time_t &_to() const { + return to; + } +protected: + /** + * Can only be constructed from the referenced container + */ + CurveIterator(const iterator_t &base, + const container_t *container, + const curve_time_t &from, + const curve_time_t &to) : + _base{base}, + container{container}, + from{from}, + to{to} {} + +protected: + /// The iterator this is currently referring to. + iterator_t _base; + /// The base container. + const container_t *container; + + /// The time, from where this iterator started to iterate. + curve_time_t from; + /// The time, to where this iterator will iterate. + curve_time_t to; +}; + + +}} // openage::curve diff --git a/libopenage/curve/map.cpp b/libopenage/curve/map.cpp new file mode 100644 index 0000000000..a1713e56c8 --- /dev/null +++ b/libopenage/curve/map.cpp @@ -0,0 +1,10 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "map.h" + +namespace openage { +namespace curve { + +// This file is intended to be empty + +}} // namespace openage::curve diff --git a/libopenage/curve/map.h b/libopenage/curve/map.h new file mode 100644 index 0000000000..e523d8f3ce --- /dev/null +++ b/libopenage/curve/map.h @@ -0,0 +1,203 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "curve.h" +#include "map_filter_iterator.h" + +#include +#include + +namespace openage { +namespace curve { + +/** + * Map that keeps track of the lifetime of the contained elements. + * make shure, that no key is never reused. + */ +template +class UnorderedMap { + /** Internal container to access all data and metadata */ + struct map_element { + val_t value; + curve_time_t alive; + curve_time_t dead; + map_element (val_t v, const curve_time_t &a, const curve_time_t &d) + : value(v), + alive(a), + dead(d) {} + }; + /** the magic is inside here */ + std::unordered_map container; + +public: + // Using does not work with templates + typedef typename std::unordered_map::const_iterator const_iterator; + + // TODO return an std::optional here + std::pair> + operator()(const curve_time_t&, const key_t &) const; + + // TODO return an std::optional here. + std::pair> + at(const curve_time_t &, const key_t &) const; + + MapFilterIterator + begin(const curve_time_t &e = std::numeric_limits::max()) const; + + MapFilterIterator + end(const curve_time_t &e = std::numeric_limits::max()) const; + + MapFilterIterator + insert(const curve_time_t &birth, const key_t &, const val_t &); + + MapFilterIterator + insert(const curve_time_t &birth, const curve_time_t &death, const key_t &key, const val_t &value); + + MapFilterIterator + between(const curve_time_t &start, const curve_time_t &to) const; + + void birth(const curve_time_t &, const key_t &); + void birth(const curve_time_t &, + const MapFilterIterator &); + + void kill(const curve_time_t &, const key_t &); + void kill(const curve_time_t &, + const MapFilterIterator &); + + // remove all dead elements before that point in time + void clean(const curve_time_t &); + + void __attribute__((noinline)) dump() { + for (auto i : container) { + std::cout << "Element: " << i.second.value << std::endl;; + } + } +}; + +template +std::pair>> +UnorderedMap::operator()(const curve_time_t &time, + const key_t &key) const { + return this->at(time, key); +} + +template +std::pair>> +UnorderedMap::at(const curve_time_t & time, + const key_t & key) const { + auto e = this->container.find(key); + if (e != this->container.end() && e->second.alive <= time && e->second.dead >time) { + return std::make_pair( + true, + MapFilterIterator>( + e, + this, + time, + std::numeric_limits::max())); + } else { + return std::make_pair( + false, + this->end(time)); + } +} + +template +MapFilterIterator> +UnorderedMap::begin(const curve_time_t &time) const { + return MapFilterIterator>( + this->container.begin(), + this, + time, + std::numeric_limits::max()); +} + +template +MapFilterIterator> +UnorderedMap::end(const curve_time_t &time) const { + return MapFilterIterator>( + this->container.end(), + this, + -std::numeric_limits::max(), + time); +} + +template +MapFilterIterator> +UnorderedMap::between(const curve_time_t &from, const curve_time_t &to) const { + auto it = MapFilterIterator>( + this->container.begin(), + this, + from, + to); + + if (!it.valid()) { + ++it; + } + return it; +} + +template +MapFilterIterator> +UnorderedMap::insert(const curve_time_t &alive, + const key_t &key, + const val_t &value) { + return this->insert( + alive, + std::numeric_limits::max(), + key, + value); +} + +template +MapFilterIterator> +UnorderedMap::insert(const curve_time_t &alive, + const curve_time_t &dead, + const key_t &key, + const val_t &value) { + map_element e(value, alive, dead); + auto it = this->container.insert(std::make_pair(key, e)); + return MapFilterIterator>( + it.first, + this, + alive, + dead); +} + +template +void UnorderedMap::birth(const curve_time_t &time, + const key_t &key) { + auto it = this->container.find(key); + if (it != this->container.end()) { + it->second.alive = time; + } +} + +template +void UnorderedMap::birth(const curve_time_t &time, + const MapFilterIterator &it) { + it->second.alive = time; +} + +template +void UnorderedMap::kill(const curve_time_t &time, + const key_t &key) { + auto it = this->container.find(key); + if (it != this->container.end()) { + it->second.dead = time; + } +} + +template +void UnorderedMap::kill(const curve_time_t &time, + const MapFilterIterator &it) { + it->second.dead = time; +} + +template +void UnorderedMap::clean(const curve_time_t &) { + // TODO save everything to a file and be happy. +} + +}} // openage::curve diff --git a/libopenage/curve/map_filter_iterator.cpp b/libopenage/curve/map_filter_iterator.cpp new file mode 100644 index 0000000000..17b743f145 --- /dev/null +++ b/libopenage/curve/map_filter_iterator.cpp @@ -0,0 +1,10 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "map_filter_iterator.h" + +namespace openage { +namespace curve { + +// This file is intended to be empty + +}} // namespace openage::curve diff --git a/libopenage/curve/map_filter_iterator.h b/libopenage/curve/map_filter_iterator.h new file mode 100644 index 0000000000..3328172ea2 --- /dev/null +++ b/libopenage/curve/map_filter_iterator.h @@ -0,0 +1,66 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "curve.h" +#include "iterator.h" + +#include +#include + +namespace openage { +namespace curve { + +/** + * A filtering operator to iterate over all elements of a map whose elements + * exist for a certain livespan. The range where to iterate is given at + * construction. + * + * It depends on key_t and val_t as map-parameters, container_t is the container + * to operate on and the function valid_f, that checks if an element is alive. + */ +template +class MapFilterIterator : + public CurveIterator +{ +public: + typedef typename container_t::const_iterator iterator_t; + + /** + * Construct the iterator from its boundary conditions: time and container + */ + MapFilterIterator(const iterator_t &base, + const container_t *container, + const curve_time_t &from, + const curve_time_t &to) : + CurveIterator(base, container, from, to) {} + + MapFilterIterator(const MapFilterIterator &) = default; + + using CurveIterator::operator=; + + virtual bool valid() const override { + return this->base()->second.alive >= this->from + && this->base()->second.dead < this->to; + } + + /** + * Get the value behind the iterator. + * Nicer way of accessing it beside operator *. + */ + val_t const &value() const override { + return this->base()->second.value; + } + + /** + * Get the key pointed to by this iterator. + */ + const key_t &key() const { + return this->base()->first; + } + +}; + +}} // openage::curve diff --git a/libopenage/curve/queue.cpp b/libopenage/curve/queue.cpp new file mode 100644 index 0000000000..744674ac7d --- /dev/null +++ b/libopenage/curve/queue.cpp @@ -0,0 +1,10 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "queue.h" + +namespace openage { +namespace curve { + +// This file is intended to be empty + +}} // namespace openage::curve diff --git a/libopenage/curve/queue.h b/libopenage/curve/queue.h new file mode 100644 index 0000000000..47cd10501a --- /dev/null +++ b/libopenage/curve/queue.h @@ -0,0 +1,186 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "curve.h" +#include "queue_filter_iterator.h" + +#include +#include + +namespace openage { +namespace curve { + +/** + * A container that manages events on a timeline. Every event has exactly one + * time it will happen. + * This container can be used to store interactions + */ +template +class Queue { + struct queue_wrapper { + curve_time_t _time; + _T value; + + queue_wrapper(const curve_time_t &time, const _T &value) : + _time{time}, + value{value} {} + + curve_time_t time() const { + return _time; + } + }; +public: + typedef typename std::deque container_t; + typedef typename container_t::const_iterator const_iterator; + typedef typename container_t::const_iterator iterator; + + // Reading Access + const _T &front(const curve_time_t &) const; + + // Modifying access + QueueFilterIterator<_T, Queue<_T>> begin( + const curve_time_t &t = -std::numeric_limits::max()) const; + + QueueFilterIterator<_T, Queue<_T>> end( + const curve_time_t &t = std::numeric_limits::max()) const; + + QueueFilterIterator<_T, Queue<_T>> between( + const curve_time_t &begin = std::numeric_limits::max(), + const curve_time_t &end = std::numeric_limits::max()) const; + + //QueueFilterIterator<_T, Queue<_T>> + void erase(const CurveIterator<_T, Queue<_T>> &); + + QueueFilterIterator<_T, Queue<_T>> insert(const curve_time_t &, const _T &e); + + void clear(); + + void clean(const curve_time_t &); + + void __attribute__((noinline)) dump() { + for (auto i : container) { + std::cout << i.value << " at " << i.time() << std::endl; + } + } + +private: + container_t container; +}; + + +template +const _T &Queue<_T>::front(const curve_time_t &) const { + return container.front(); +} + + +template +QueueFilterIterator<_T, Queue<_T>> Queue<_T>::begin(const curve_time_t &t) const +{ + for (auto it = this->container.begin(); it != this->container.end(); ++it) { + if (it->time() >= t) { + return QueueFilterIterator<_T, Queue<_T>>( + it, + this, + t, + std::numeric_limits::max()); + } + } + + return this->end(t); +} + + +template +QueueFilterIterator<_T, Queue<_T>> Queue<_T>::end(const curve_time_t &t) const +{ + return QueueFilterIterator<_T, Queue<_T>>( + container.end(), + this, + t, + std::numeric_limits::max()); +} + + +template +QueueFilterIterator<_T, Queue<_T>> Queue<_T>::between( + const curve_time_t &begin, + const curve_time_t &end) const +{ + auto it = QueueFilterIterator<_T, Queue<_T>>( + container.begin(), + this, + begin, + end); + if (!container.empty() && !it.valid()) { + ++it; + } + return it; +} + + +template +/*QueueFilterIterator<_T, Queue<_T>>*/ void Queue<_T>::erase(const CurveIterator<_T, Queue<_T>> &t) +{ + auto it = container.erase(t.base()); + /*auto ct = QueueFilterIterator<_T, Queue<_T>>( + it, + this, + t._from(), + t._to()); + if (ct.base() != container.end() && !ct.valid()) { + ++ct; + }*/ + + return; +} + + +template +QueueFilterIterator<_T, Queue<_T>> Queue<_T>::insert( + const curve_time_t &time, + const _T &e) { + + const_iterator insertion_point = this->container.end(); + for (auto it = this->container.begin(); it != this->container.end(); ++it) { + if (time < it->time()) { + insertion_point = this->container + .insert(it, queue_wrapper(time, e)); + break; + } + } + if (insertion_point == this->container.end()) { + insertion_point = this->container.insert(this->container.end(), + queue_wrapper(time, e)); + } + + auto ct = QueueFilterIterator<_T, Queue<_T>>( + insertion_point, + this, + time, std::numeric_limits::max()); + + if (!ct.valid()) { + ++ct; + } + return ct; +} + + +template +void Queue<_T>::clear() { + this->container.clear(); +} + + +template +void Queue<_T>::clean(const curve_time_t &time) { + for (auto it = this->container.begin(); + it != this->container.end() + && it->time() < time; + it = this->container.erase(it)) + {} +} + + +}} // openage::curve diff --git a/libopenage/curve/queue_filter_iterator.cpp b/libopenage/curve/queue_filter_iterator.cpp new file mode 100644 index 0000000000..d80d8a581d --- /dev/null +++ b/libopenage/curve/queue_filter_iterator.cpp @@ -0,0 +1,10 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "queue_filter_iterator.h" + +namespace openage { +namespace curve { + +// This file is intended to be empty + +}} // namespace openage::curve diff --git a/libopenage/curve/queue_filter_iterator.h b/libopenage/curve/queue_filter_iterator.h new file mode 100644 index 0000000000..c752b4fcd6 --- /dev/null +++ b/libopenage/curve/queue_filter_iterator.h @@ -0,0 +1,52 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "curve.h" +#include "iterator.h" + +#include +#include + +namespace openage { +namespace curve { + +/** + * A filtering operator to iterate over all elements of a queue whose elements + * exist at exactly one point of time, the range where to iterate is given at + * construction. + * + * It depends on val_t as its value type, container_t is the container + * to operate on and the function valid_f, that checks if an element is alive. + */ +template +class QueueFilterIterator : + public CurveIterator +{ +public: + typedef typename container_t::const_iterator const_iterator; + + /** + * Construct the iterator from its boundary conditions: time and container + */ + QueueFilterIterator(const const_iterator &base, + const container_t *base_container, + const curve_time_t &from, + const curve_time_t &to) : + CurveIterator(base, base_container, from, to) {} + + virtual bool valid() const override { + if (this->container->end().base() != this->base()) { + return this->base()->time() >= this->from && this->base()->time() < this->to; + } + return false; + } + + const val_t &value() const override { + const auto &a = (*this->base()); + return a.value; + } +}; + +}} // openage::curve diff --git a/libopenage/curve/test/CMakeLists.txt b/libopenage/curve/test/CMakeLists.txt new file mode 100644 index 0000000000..7538eb49ea --- /dev/null +++ b/libopenage/curve/test/CMakeLists.txt @@ -0,0 +1,8 @@ +link_libraries(tube) + +add_sources(libopenage + test_container.cpp + test_curve_types.cpp + test_events.cpp + test_serialization.cpp +) diff --git a/libopenage/curve/test/test_container.cpp b/libopenage/curve/test/test_container.cpp new file mode 100644 index 0000000000..91d5232913 --- /dev/null +++ b/libopenage/curve/test/test_container.cpp @@ -0,0 +1,214 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "../../testing/testing.h" +#include "../curve.h" +#include "../continuous.h" +#include "../discrete.h" +#include "../queue.h" +#include "../map.h" + +#include +#include + +namespace openage { +namespace curve { +namespace tests { + +struct map_test_element { + volatile int value; + + map_test_element(int v) : + value(v) {} + + bool operator != (int rhs) { + return this->value != rhs; + } + +}; + +std::ostream &operator << (std::ostream &o, const map_test_element &e) { + o << e.value; + return o; +} + +template +void dump(const std::map &map) { + for (auto i : map) { + std::cout << i.first << ": " << i.second << std::endl; + } +} + +void test_map() { + static_assert(std::is_copy_constructible>>::value, + "UnorderedMapIterator not Copy Constructable able"); + static_assert(std::is_copy_assignable>>::value, + "UnorderedMapIterator not Copy Assignable"); + + UnorderedMap map; + map.insert(0, 10, 0, 0); + map.insert(5, 10, 5, 1); + map.insert(100, 200, 200, 2); + + // Basic tests test lookup in the middle of the range. + { + auto t = map.at(2, 0); //At timestamp 2 element 0 + TESTEQUALS(t.first, true); + TESTEQUALS(t.second.value(), 0); + t = map.at(20, 5); + TESTEQUALS(t.first, false); + } + { + auto t = map.at(7, 5); + TESTEQUALS(t.first, true); + TESTEQUALS(t.second.value(), 1); + t = map.at(20, 5); + TESTEQUALS(t.first, false); + t = map.at(2, 5); + TESTEQUALS(t.first, false); + } + { + auto t = map.at(150, 200); + TESTEQUALS(t.first, true); + TESTEQUALS(t.second.value(), 2); + t = map.at(500, 200); + TESTEQUALS(t.first, false); + t = map.at(5, 200); + TESTEQUALS(t.first, false); + } + // test 2.0: test at the boundaries + { + auto t = map.at(0, 0); + TESTEQUALS(t.first, true); + TESTEQUALS(t.second.value(), 0); + t = map.at(10, 0); + TESTEQUALS(t.first, false); + } + { + auto t = map.at(5, 5); + TESTEQUALS(t.first, true); + TESTEQUALS(t.second.value(), 1); + t = map.at(10, 5); + TESTEQUALS(t.first, false); + } + { + auto t = map.at(100, 200); + TESTEQUALS(t.first, true); + TESTEQUALS(t.second.value(), 2); + t = map.at(200, 200); + TESTEQUALS(t.first, false); + } + // Test 3.0 Iterations + { + // Iteration tests + std::map reference; + reference[0] = 0; + reference[5] = 1; + reference[200] = 2; + for (auto it = map.begin(0); it != map.end(); ++it) { // Get all + auto ri = reference.find(it.key()); + if (ri != reference.end()) { + reference.erase(ri); + } + } + TESTEQUALS(reference.empty(), true); + + reference[5] = 5; + for (auto it = map.begin(1); it != map.end(90); ++it) { + auto ri = reference.find(it.key()); + if (ri != reference.end()) { + reference.erase(ri); + } + } + TESTEQUALS(reference.empty(), true); + + reference[5] = 5; + for (auto it = map.between(1,90); it != map.end(); ++it) { + auto ri = reference.find(it.key()); + if (ri != reference.end()) { + reference.erase(ri); + } + } + TESTEQUALS(reference.empty(), true); + } +} + +void test_list() { + +} + +void test_queue() { + static_assert(std::is_copy_constructible>>::value, + "QueueIterator not Copy Constructable able"); + static_assert(std::is_copy_assignable>>::value, + "QueueIterator not Copy Assignable"); + + + + Queue q; + q.insert(0, 1); + q.insert(2, 2); + q.insert(4, 3); + q.insert(10, 4); + q.insert(100001, 5); + TESTEQUALS(*q.begin(0), 1); + TESTEQUALS(*q.begin(1), 2); + TESTEQUALS(*q.begin(2), 2); + TESTEQUALS(*q.begin(3), 3); + TESTEQUALS(*q.begin(4), 3); + TESTEQUALS(*q.begin(5), 4); + TESTEQUALS(*q.begin(10), 4); + TESTEQUALS(*q.begin(12), 5); + TESTEQUALS(*q.begin(100000), 5); + + { + std::set reference = {1,2,3}; + for (auto it = q.between(0,6); it != q.end(); ++it) { + auto ri = reference.find(it.value()); + if (ri != reference.end()) { + reference.erase(ri); + } + } + TESTEQUALS(reference.empty(), true); + } + { + std::set reference = {2,3,4}; + for (auto it = q.between(1,40); it != q.end(); ++it) { + auto ri = reference.find(it.value()); + if (ri != reference.end()) { + reference.erase(ri); + } + } + TESTEQUALS(reference.empty(), true); + } + { + std::set reference = {}; + for (auto it = q.between(30,40); it != q.end(); ++it) { + auto ri = reference.find(it.value()); + if (ri != reference.end()) { + reference.erase(ri); + } + } + TESTEQUALS(reference.empty(), true); + } + { + std::set reference = {1,2,3,4}; + for (auto it = q.between(0,40); it != q.end(); ++it) { + auto ri = reference.find(it.value()); + if (ri != reference.end()) { + reference.erase(ri); + } + } + TESTEQUALS(reference.empty(), true); + } + +} + + +void container() { + test_map(); + test_list(); + test_queue(); +} + + +}}} // openage::curve::tests diff --git a/libopenage/curve/test/test_curve_types.cpp b/libopenage/curve/test/test_curve_types.cpp new file mode 100644 index 0000000000..1aea16bbd5 --- /dev/null +++ b/libopenage/curve/test/test_curve_types.cpp @@ -0,0 +1,141 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "../../testing/testing.h" +#include "../internal/keyframe_container.h" +#include "../curve.h" +#include "../continuous.h" +#include "../discrete.h" + +#include "../events/eventmanager.h" + +#include "../../log/log.h" + +namespace openage { +namespace curve { +namespace tests { + +void curve_types() { + // Check the base container type + EventManager f; + { + KeyframeContainer c; + auto p0 = c.insert(0, 0); + auto p1 = c.insert(1, 1); + auto p2 = c.insert(10, 2); + + // last function tests without hints + TESTEQUALS(c.last(0)->value, 0); + TESTEQUALS(c.last(1)->value, 1); //last shall give >= not only > ! + TESTEQUALS(c.last(5)->value, 1); + TESTEQUALS(c.last(10)->value, 2); + TESTEQUALS(c.last(47)->value, 2); + + // last() with hints. Yes this can make a diference. we want to be + // absolutely shure! + // hint p1 + TESTEQUALS(c.last(0, p0)->value, 0); + TESTEQUALS(c.last(1, p0)->value, 1); //last shall give >= not only > ! + TESTEQUALS(c.last(5, p0)->value, 1); + TESTEQUALS(c.last(10, p0)->value, 2); + TESTEQUALS(c.last(47, p0)->value, 2); + + TESTEQUALS(c.last(0, p1)->value, 0); + TESTEQUALS(c.last(1, p1)->value, 1); //last shall give >= not only > ! + TESTEQUALS(c.last(5, p1)->value, 1); + TESTEQUALS(c.last(10, p1)->value, 2); + TESTEQUALS(c.last(47, p1)->value, 2); + + TESTEQUALS(c.last(0, p2)->value, 0); + TESTEQUALS(c.last(1, p2)->value, 1); //last shall give >= not only > ! + TESTEQUALS(c.last(5, p2)->value, 1); + TESTEQUALS(c.last(10, p2)->value, 2); + TESTEQUALS(c.last(47, p2)->value, 2); + + // Now test the basic erase() function + c.erase(c.last(1)); + + TESTEQUALS(c.last(1)->value, 0); + TESTEQUALS(c.last(5)->value, 0); + TESTEQUALS(c.last(47)->value, 2); + + c.erase_after(c.last(99)); // we dont want to have the element itself erased! + TESTEQUALS(c.last(47)->value, 2); + + c.erase_after(c.last(5)); // now since 10 > 5, element with value 2 has to be gone + TESTEQUALS(c.last(47)->value, 0); + } + + // Check the Simple Continuous type + { + Continuous c(&f, 0); + c.set_insert(0, 0); + c.set_insert(10, 1); + + TESTEQUALS(c.get(0), 0); + + TESTEQUALS_FLOAT(c.get(1), 0.1, 1e-7); + } + + { + Continuous c(&f, 0); + log::log(DBG << "Testing Continuous float"); + c.set_insert(0, 0); + log::log(DBG << "Inserting t:20, v:20"); + c.set_insert(20, 20); + + TESTEQUALS(c.get(0), 0); + TESTEQUALS(c.get(1), 1); + TESTEQUALS(c.get(7), 7); + + c.set_drop(20, 10); + log::log(DBG << "Inserting t:20, v:10. This should overwrite the old values!"); + TESTEQUALS(c.get(0), 0); + TESTEQUALS(c.get(2), 1); + TESTEQUALS(c.get(8), 4); + } + + //Check the discrete type + { + Discrete c(&f, 0); + c.set_insert(0, 0); + c.set_insert(10, 10); + + TESTEQUALS(c.get(0), 0); + TESTEQUALS(c.get(1), 0); + TESTEQUALS(c.get(10), 10); + + Discrete complex(&f, 0); + + complex.set_insert(0, "Test 0"); + complex.set_insert(10, "Test 10"); + + TESTEQUALS(complex.get(0), "Test 0"); + TESTEQUALS(complex.get(1), "Test 0"); + TESTEQUALS(complex.get(10), "Test 10"); + + } + + //check set_drop + { + Discrete c(&f, 0); + c.set_insert(0, 0); + c.set_insert(1, 1); + c.set_insert(3, 3); + + TESTEQUALS(c.get(3), 3); + + c.set_drop(2, 10); + TESTEQUALS(c.get(2), 10); + } + + // Encountered Errors + { + Continuous c(&f, 0); + c.set_insert(1, 1); + c.set_drop(1, -5); + + TESTEQUALS(c.get(1), -5); + } +} + +}}} // openage::curve::tests diff --git a/libopenage/curve/test/test_events.cpp b/libopenage/curve/test/test_events.cpp new file mode 100644 index 0000000000..20a2634b84 --- /dev/null +++ b/libopenage/curve/test/test_events.cpp @@ -0,0 +1,461 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "../events/events.h" +#include "../events/eventtarget.h" +#include "../events/eventclass.h" +#include "../events/eventmanager.h" + + +#include "../../testing/testing.h" +#include "../../log/log.h" + +#include + +#include // for strcmp + +namespace openage { + +// We have to create a temporary State due to the magic of C++ +class State { +public: + class TestObject : public curve::EventTarget { + const int _id; + public: + TestObject(curve::EventManager *manager, int id) : EventTarget(manager), _id{id}, number(0) {} + void set_number(int number, const curve::curve_time_t &time) { + this->number = number; + this->on_change(time + 1); + } + int number; + + size_t id() const override { return _id; } + void trigger_keyframe(const curve::curve_time_t &time) { this->on_pass_keyframe(time); } + }; + + State(curve::EventManager *manager) : + objectA(std::make_shared(manager, 0)) , + objectB(std::make_shared(manager, 1)) + {} + + std::shared_ptr objectA; + std::shared_ptr objectB; + + struct traceelement { + traceelement(const std::string &event, curve::curve_time_t time) : + time{time}, name{event} {} + curve::curve_time_t time; + std::string name; + }; + std::list trace; + + void log_dbg() { + log::log(DBG << "Trace: "); + for (const auto &e : this->trace) { + log::log(DBG << "T: " << e.time << ": " << e.name); + } + } +}; + +namespace curve { +namespace tests { + +class TestEventClass : public EventClass { + int idx; +public: + TestEventClass(const std::string &name, int idx) : EventClass(name, EventClass::Type::ON_CHANGE), idx{idx} {} + + void setup(const std::shared_ptr &target, + const std::shared_ptr &state) override { + switch(idx) { + case 0: + add_dependency(target, state->objectB); + break; + case 1: + add_dependency(target, state->objectA); + break; + } + } + + void call(EventManager *, + const std::shared_ptr &target, + const std::shared_ptr &state, + const curve_time_t &time, + const EventClass::param_map &/*param*/) override { + (void)target; + switch (idx) { + case 0: { + auto t = std::dynamic_pointer_cast(target); + state->objectA->set_number(t->number + 1, time); + log::log(DBG << "I am Event A. Setting number to " << state->objectA->number); + state->trace.emplace_back("A", time); + } break; + case 1: { + auto t = std::dynamic_pointer_cast(target); + state->objectB->set_number(t->number + 1, time); + log::log(DBG << "I am Event B. Setting number to " << state->objectB->number); + state->trace.emplace_back("B", time); + } break; + } + } + + curve_time_t recalculate_time(const std::shared_ptr &target, + const std::shared_ptr &state, + const curve::curve_time_t &at) override { + // TODO recalculate a hit time + (void)target; + (void)state; + return at + 2; + } +}; + +class TestEventClassTwo : public EventClass { +public: + TestEventClassTwo(const std::string &name) : EventClass(name, EventClass::Type::ON_CHANGE) {} + + void setup(const std::shared_ptr &target, const std::shared_ptr &state) override { + add_dependency(target, state->objectA); + } + + void call(EventManager *, + const std::shared_ptr &target, + const std::shared_ptr &state, + const curve_time_t &time, + const EventClass::param_map &/*param*/) override { + auto t = std::dynamic_pointer_cast(target); + state->objectB->set_number(t->number + 1, time); + log::log(DBG << "I am EventClassTwo. Setting B.number to " << state->objectB->number); + state->trace.emplace_back("B", time); + } + + curve_time_t recalculate_time(const std::shared_ptr &target, + const std::shared_ptr &state, + const curve::curve_time_t &at) override { + // TODO recalculate a hit time + (void)target; + (void)state; + return at + 1; + } +}; + +class EventTypeTestClass : public EventClass { +public: + EventTypeTestClass(const std::string &name, EventClass::Type type) : EventClass(name, type) {} + + void setup(const std::shared_ptr &target, + const std::shared_ptr &state) override { + add_dependency(target, state->objectA); + } + + void call(EventManager *, + const std::shared_ptr &target, + const std::shared_ptr &state, + const curve_time_t &time, + const EventClass::param_map &/*param*/) override { + switch(this->type) { + case EventClass::Type::ON_CHANGE: + case EventClass::Type::ON_CHANGE_IMMEDIATELY: + case EventClass::Type::ON_KEYFRAME: + case EventClass::Type::ON_EXECUTE: + case EventClass::Type::ONCE: + break; + } + auto t = std::dynamic_pointer_cast(target); + log::log(DBG << this->id() << " got called on " << t->id() + << " with number " << t->number << " at " << time); + state->trace.emplace_back(this->id(), time); + (void)time; + (void)state; + } + + curve_time_t recalculate_time(const std::shared_ptr &target, + const std::shared_ptr &state, + const curve::curve_time_t &at) override { + (void)target; + (void)state; + switch(this->type) { + case EventClass::Type::ON_CHANGE: + return at+1; // Execute 1 after the change (usually it is neccessary to recalculate a colission + case EventClass::Type::ON_CHANGE_IMMEDIATELY: + TESTFAILMSG("ON_CHANGE_IMMEDIATELY does not recalculate time!"); + return 0; // This is ignored anyway + case EventClass::Type::ON_KEYFRAME: + TESTFAILMSG("ON_KEYFRAME does not recalculate time!"); + return 0; // This is ignored anyway + case EventClass::Type::ON_EXECUTE: + return at + 5; // This will force the execution every 5ms + case EventClass::Type::ONCE: + return 10; // even if data changed it will happen at the given time! + } + return at; + } +}; + +void events2() { + log::log(DBG << "------------- [ Starting Test: Basic Ping Pong ] ------------"); + // test destruction + std::weak_ptr destruction_test_state; + { + // Test with one event class + EventManager manager; + + manager.add_class(std::make_shared("testOnA", 0)); + manager.add_class(std::make_shared("testOnB", 1)); + + auto state = std::make_shared(&manager); + destruction_test_state = state; + // One must not start the game at 0 - this leads to randomness in execution + manager.on("testOnB", state->objectB, state, 1); + manager.on("testOnA", state->objectA, state, 1); + + // It is expected, that A and B hand over the "changed" events between each other + state->objectA->set_number(0, 0); + for (int i = 1; i < 10; ++i) { + manager.execute_until(i * 2, state); + } + + state->log_dbg(); + + int i = 0; + curve_time_t last_time = 0; + for (auto it = state->trace.begin(); it != state->trace.end(); ++it, ++i) { + const auto &e = *it; + if (last_time > e.time) { + TESTFAILMSG("You broke the time continuum: one shall not execute randomly!"); + } + last_time = e.time; + switch(i) { + case 0: TESTEQUALS(e.name, "B"); TESTEQUALS(e.time, 3); break; + case 1: TESTEQUALS(e.name, "A"); TESTEQUALS(e.time, 6); break; + case 2: TESTEQUALS(e.name, "B"); TESTEQUALS(e.time, 9); break; + case 3: TESTEQUALS(e.name, "A"); TESTEQUALS(e.time, 12); break; + case 4: TESTEQUALS(e.name, "B"); TESTEQUALS(e.time, 15); break; + default: TESTFAILMSG("Too many elements in stack trace"); break; + } + } + } + if (!destruction_test_state.expired()) { + TESTFAILMSG("Test Failed because State was not automatically destructed"); + } + + log::log(DBG << "------------- [ Starting Test: Two Event Ping Pong ] ------------"); + { + // Test with two event classes to check interplay + // Test with one event class + EventManager manager; + + manager.add_class(std::make_shared("testOnA", 0)); + manager.add_class(std::make_shared("testOnB")); + + auto state = std::make_shared(&manager); + // One must not start the game at 0 - this leads to randomness in execution + manager.on("testOnB", state->objectB, state, 1); + manager.on("testOnA", state->objectA, state, 1); + + // It is expected, that A and B hand over the "changed" events between each other + state->objectA->set_number(0, 1); + for (int i = 1; i < 10; ++i) { + manager.execute_until(i * 2, state); + } + + state->log_dbg(); + + int i = 0; + curve_time_t last_time = 0; + for (auto it = state->trace.begin(); it != state->trace.end(); ++it, ++i) { + const auto &e = *it; + if (last_time > e.time) { + TESTFAILMSG("You broke the time continuum: one shall not execute randomly!"); + } + last_time = e.time; + switch(i) { + case 0: TESTEQUALS(e.name, "B"); TESTEQUALS(e.time, 3); break; + case 1: TESTEQUALS(e.name, "A"); TESTEQUALS(e.time, 6); break; + case 2: TESTEQUALS(e.name, "B"); TESTEQUALS(e.time, 8); break; + case 3: TESTEQUALS(e.name, "A"); TESTEQUALS(e.time, 11); break; + case 4: TESTEQUALS(e.name, "B"); TESTEQUALS(e.time, 13); break; + case 5: TESTEQUALS(e.name, "A"); TESTEQUALS(e.time, 16); break; + default: TESTFAILMSG("Too many elements in stack trace"); break; + } + } + } + + log::log(DBG << "------------- [ Starting Test: Complex Event Types ] ------------"); + // Now set up a more complex test to test the different event types + { + EventManager manager; + manager.add_class(std::make_shared( + "onChange", + EventClass::Type::ON_CHANGE)); + manager.add_class(std::make_shared( + "onChangeImm", + EventClass::Type::ON_CHANGE_IMMEDIATELY)); + manager.add_class(std::make_shared( + "onKeyframe", + EventClass::Type::ON_KEYFRAME)); + manager.add_class(std::make_shared( + "onExecute", + EventClass::Type::ON_EXECUTE)); + manager.add_class(std::make_shared( + "once", + EventClass::Type::ONCE)); + + auto state = std::make_shared(&manager); + + // One must not start the game at 0 - this leads to randomness in execution + manager.on("onChange", state->objectA, state, 4); + manager.on("onChangeImm", state->objectA, state, 1); + manager.on("onKeyframe", state->objectA, state, 1); + manager.on("onExecute", state->objectA, state, 5); + manager.on("once", state->objectA, state, 10); + + log::log(DBG << "##### SETUP DONE "); + // Without anything happen and until time 0 nothing will happen + { + manager.execute_until(0, state); // Expected: onChangeImm(1) [ we changed data at t=1] + TESTEQUALS(state->trace.empty(), true); + } + state->objectA->set_number(0, 1); + { + manager.execute_until(2, state); // Expected: nothing + TESTEQUALS(state->trace.empty(), true); + } + { + manager.execute_until(2, state); // Expected: nothing + TESTEQUALS(state->trace.empty(), true); + } + log::log(DBG << "##### INIT DONE "); + log::log(DBG << "Triggering Keyframe at 1"); + state->objectA->trigger_keyframe(1); + { + manager.execute_until(2, state); // Expected: onKeyframe(1) + state->log_dbg(); + TESTEQUALS(state->trace.empty(), false); + auto it = state->trace.begin(); + if (it->name != "onKeyframe") + TESTFAILMSG("Unexpected Event: " << it->name << " expected onKeyframe"); + TESTEQUALS(it->time, 1); + it ++; + TESTEQUALS(it == state->trace.end(), true); + state->trace.clear(); + } + { + manager.execute_until(5, state); // Expected: onChangeImm(2), onChange(2) and onExecute(5) + TESTEQUALS(state->trace.empty(), false); + auto it = state->trace.begin(); + if (it->name != "onChangeImm") + TESTFAILMSG("Unexpected Event: " << it->name << " expected onChangeImm"); + TESTEQUALS(it->time, 2); + it ++; + if (it->name != "onChange") + TESTFAILMSG("Unexpected Event: " << it->name << " expected onChange"); + TESTEQUALS(it->time, 3); + it ++; + TESTEQUALS(it == state->trace.end(), true); + state->trace.clear(); + } + { + manager.execute_until(11, state); // Expected: onExecute(10), once(10) + TESTEQUALS(state->trace.empty(), false); + auto it = state->trace.begin(); + if (it->name != "onExecute") + TESTFAILMSG("Unexpected Event: " << it->name << " expected onExecute"); + TESTEQUALS(it->time, 10); + it ++; + if (it->name != "once") + TESTFAILMSG("Unexpected Event: " << it->name << " expected once"); + TESTEQUALS(it->time, 10); + it ++; + TESTEQUALS(it == state->trace.end(), true); + state->trace.clear(); + } + + log::log(DBG << "changing the value at 12"); + state->objectA->set_number(1, 12); + { + manager.execute_until(15, state); // Expected: onChangeImm, onChange(12) + TESTEQUALS(state->trace.empty(), false); + auto it = state->trace.begin(); + if (it->name != "onChangeImm") + TESTFAILMSG("Unexpected Event: " << it->name << " expected onChangeImm"); + TESTEQUALS(it->time, 13); + it ++; + if (it->name != "onChange") + TESTFAILMSG("Unexpected Event: " << it->name << " expected onChange"); + TESTEQUALS(it->time, 14); + it ++; + TESTEQUALS(it == state->trace.end(), true); + state->trace.clear(); + } + { + manager.execute_until(16, state); // Expected: onExecute(15) + TESTEQUALS(state->trace.empty(), false); + auto it = state->trace.begin(); + if (it->name != "onExecute") + TESTFAILMSG("Unexpected Event: " << it->name << " expected onExecute"); + TESTEQUALS(it->time, 15); + it ++; + TESTEQUALS(it == state->trace.end(), true); + state->trace.clear(); + } + } + log::log(DBG << "------------- [ Starting Test: Event parameter Mapping ] ------------"); + { + class EventParameterMapTestClass : public curve::EventClass { + public: + EventParameterMapTestClass() : EventClass("EventParameterMap", EventClass::Type::ONCE) {} + + void setup(const std::shared_ptr &/*target*/, + const std::shared_ptr &/*state*/) override { + } + + void call(EventManager *, + const std::shared_ptr &/*target*/, + const std::shared_ptr &/*state*/, + const curve_time_t &/*time*/, + const EventClass::param_map ¶m) override { + log::log(DBG << "Testing unknown parameter"); + TESTEQUALS(param.contains("tomato"), false); + TESTEQUALS(param.check_type("tomato"), false); + TESTEQUALS(param.get("tomato", 1), 1); + TESTEQUALS(param.get("tomato"), 0); + TESTEQUALS(param.get("tomato", "test"), "test"); + TESTEQUALS(param.get("tomato"), ""); + + log::log(DBG << "Testing Integer parameter"); + TESTEQUALS(param.contains("testInt"), true); + TESTEQUALS(param.check_type("testInt"), true); + TESTEQUALS(param.get("testInt"), 1); + // FIXME: This should hurt you!!! + TESTEQUALS(param.get("testInt", "int"), "int"); + + log::log(DBG << "Testing char* parameter"); + TESTEQUALS(param.contains("testString"), true); + TESTEQUALS(param.check_type("testString"), true); + TESTEQUALS(strcmp(param.get("testString"), "string"), 0); + + log::log(DBG << "Testing std::string parameter"); + TESTEQUALS(param.contains("testStdString"), true); + TESTEQUALS(param.check_type("testStdString"), true); + TESTEQUALS(param.get("testStdString"), "stdstring"); + } + curve_time_t recalculate_time(const std::shared_ptr &/*target*/, + const std::shared_ptr &/*state*/, + const curve::curve_time_t &at) override { + return at; + } + }; + + curve::EventManager manager; + manager.add_class(std::make_shared()); + auto state = std::make_shared(&manager); + manager.on("EventParameterMap", state->objectA, state, 1, { + {"testInt", 1}, + {"testStdString", std::string("stdstring")}, + {"testString", "string"}}); + manager.execute_until(10, state); + + + } +} + +}}} // namespace openage::curve::tests diff --git a/libopenage/curve/test/test_serialization.cpp b/libopenage/curve/test/test_serialization.cpp new file mode 100644 index 0000000000..f446cb1f7f --- /dev/null +++ b/libopenage/curve/test/test_serialization.cpp @@ -0,0 +1,13 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "../../testing/testing.h" + +namespace openage { +namespace curve { +namespace tests { + +void serialization() { + +} + +}}} // openage::curve::tests diff --git a/libopenage/gamestate/gamestate.h b/libopenage/gamestate/gamestate.h new file mode 100644 index 0000000000..862dc79dcd --- /dev/null +++ b/libopenage/gamestate/gamestate.h @@ -0,0 +1,111 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../curve/events/eventtarget.h" +#include "../curve/map.h" +#include "../util/vector.h" + +#include + +namespace openage { +namespace curvepong { + +using unit_id_t = uint64_t; +using property_id_t = uint64_t; +using player_id_t = uint64_t; + +class NyanObserver : curve::EventTarget { +public: + NyanObserver(const EventManager &mgr, const nyan::Object &object) : + EventTarget(mgr) { + object.onchange([this](const curve::curve_time_t &at) { + this->onchange(at); + }); + } +}; + +struct NyanIdentifier { + nyan::fqon_t fqon; + nyan::memberid_t member; +}; + + +class Player { +public: + nyan::Object civilisation; + + std::set units; +}; + + +class Ability { +public: + Ability(const nyan::Object &); + nyan::Object type; +}; + +class Property : curve::EventTarget { +public: + Property(const EventManager *mgr) : + EventTarget(mgr) {}; +}; + +/** + * Element in the game universe. + * + * Everything that is somehow relevant in the game is a Unit. + */ +class Unit : curve::EventTarget { +public: + Unit (unit_id_t, const nyan::Object &); + + // Has to be kept in sync with the player list + curve::Discrete owning_player; + + /** The least common denominator is the position of an object. */ + std::shared_ptr>> position; + + /** + * List of per-unit variables that are constructed from abilities and + * properties from nyan + */ + curve::unordered_map> properties; + + /** + * property tracking within the nyan tree + * + * properties watched by this unit in nyan. This is used to traverse the + * on-change events from nyan into the event library. + */ + std::unordered_map> observed; + + /** + * The referenced unit type + */ + nyan::Object type; + + /** + * The unique identifier of this object. + */ + unit_id_t id; +}; + +class Universe { +public: + Universe(const nyan::Object &); + nyan::Object data; + + curve::unordered_map units; + + unit_id_t next_unit_id; +}; + +class Game { +public: + Universe universe; + EventManager evntmgr; + std::unordered_map players; +}; + +}} diff --git a/openage/CMakeLists.txt b/openage/CMakeLists.txt index 12d46f6f5c..89a54c3ba8 100644 --- a/openage/CMakeLists.txt +++ b/openage/CMakeLists.txt @@ -20,5 +20,5 @@ add_subdirectory(cppinterface) add_subdirectory(cvar) add_subdirectory(game) add_subdirectory(log) -add_subdirectory(util) add_subdirectory(testing) +add_subdirectory(util) diff --git a/openage/cabextract/cab.py b/openage/cabextract/cab.py index fa2f3500f5..bb078f554f 100644 --- a/openage/cabextract/cab.py +++ b/openage/cabextract/cab.py @@ -336,9 +336,7 @@ def read_folder_headers(self, cab): compressed_data_stream, window_bits=window_bits, reset_interval=0) - folder.plain_stream = StreamSeekBuffer(unseekable_plain_stream) - else: raise Exception("Unknown compression type %d" % compression_type) diff --git a/openage/testing/main.py b/openage/testing/main.py index 2837e3426e..4fdb54bb0b 100644 --- a/openage/testing/main.py +++ b/openage/testing/main.py @@ -49,6 +49,7 @@ def init_subparser(cli): cli.add_argument("test", nargs='*', help="run this test") +# pylint: disable=too-many-branches def process_args(args, error): """ Processes the given args, detecting errors. """ if not (args.run_all_tests or args.demo or args.test or args.benchmark): @@ -90,8 +91,15 @@ def process_args(args, error): if (test, 'test') not in test_list: # If the test was not found explicit in the testlist, try to find # all prefixed tests and run them instead. - matched = [elem[0] for elem in test_list - if elem[0].startswith(test) and elem[1] == "test"] + matched = False + for candidate_name, candidate_type in test_list: + if candidate_name.startswith(test) and candidate_type == "test": + matched = True + args.test.append(candidate_name) + if not matched: + error("no such test: " + test) + if matched: + args.test.remove(test) if matched: args.test.extend(matched) diff --git a/openage/testing/testlist.py b/openage/testing/testlist.py index 2f230b38ff..e3f12da991 100644 --- a/openage/testing/testlist.py +++ b/openage/testing/testlist.py @@ -68,24 +68,29 @@ def tests_cpp(): """ yield "openage::coord::tests::coord" + yield "openage::curve::tests::container" + yield "openage::curve::tests::curve_types" + yield "openage::curve::tests::events2" + yield "openage::curve::tests::serialization" yield "openage::datastructure::tests::constexpr_map" yield "openage::datastructure::tests::pairing_heap" + yield "openage::input::tests::parse_event_string", "keybinds parsing" + yield "openage::input::tests::parse_event_string", "keybinds parsing" yield "openage::job::tests::test_job_manager" yield "openage::path::tests::path_node", "pathfinding" - yield "openage::pyinterface::tests::pyobject" yield "openage::pyinterface::tests::err_py_to_cpp" + yield "openage::pyinterface::tests::pyobject" yield "openage::renderer::tests::font" yield "openage::renderer::tests::font_manager" yield "openage::rng::tests::run" + yield "openage::util::tests::array_conversion" yield "openage::util::tests::constinit_vector" yield "openage::util::tests::enum_" yield "openage::util::tests::init" yield "openage::util::tests::matrix" yield "openage::util::tests::quaternion" - yield "openage::util::tests::vector" yield "openage::util::tests::siphash" - yield "openage::util::tests::array_conversion" - yield "openage::input::tests::parse_event_string", "keybinds parsing" + yield "openage::util::tests::vector" def demos_cpp(): @@ -105,6 +110,8 @@ def demos_cpp(): "translates a Python exception to C++") yield ("openage::pyinterface::tests::pyobject_demo", "a tiny interactive interpreter using PyObjectRef") + yield ("openage::curvepong::demo", + "a pong game implemented in curves") def benchmark_cpp():