-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
584 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
add_sources(libopenage | ||
entities_conductor.cpp | ||
entities_conductor_interpolator.cpp | ||
occurence_curve.cpp | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// Copyright 2015-2016 the openage authors. See copying.md for legal info. | ||
|
||
#include "entities_conductor.h" | ||
|
||
#include <algorithm> | ||
|
||
#include "../error/error.h" | ||
|
||
namespace openage { | ||
namespace curve { | ||
|
||
EntitiesConductor::EntitiesConductor(std::function<void(int, int, coord::phys2)> emerge, std::function<void(int)> vanish) | ||
: | ||
emerge{emerge}, | ||
vanish{vanish} { | ||
} | ||
|
||
void EntitiesConductor::jump(GameClock::moment from, GameClock::moment to) { | ||
// TODO: call appear/vanish for every SPAWN/DIE (depending on the time direction) | ||
// TODO: for each property find closest curve to 'to' that changes it, then apply the last value or a value that is sampled from curve at the 'to' moment | ||
} | ||
|
||
Prediction EntitiesConductor::predict_migration(GameClock::moment current_time, GameClock::moment other_time) const { | ||
const auto prior = std::min(current_time, other_time); | ||
const auto forth = std::max(current_time, other_time); | ||
|
||
const auto cmp_start_cp = [](const OccurenceCurve &c, GameClock::moment p) { return c.origin.time < p; }; | ||
const auto cmp_start_pc = [](GameClock::moment p, const OccurenceCurve &c) { return p < c.origin.time; }; | ||
|
||
const auto cmp_end_ip = [this](size_t index, GameClock::moment p) { return end_time(this->history[index]) < p; }; | ||
const auto cmp_end_pi = [this](GameClock::moment p, size_t index) { return p < end_time(this->history[index]); }; | ||
|
||
ENSURE(this->history.size() == this->history_sorted_by_ends.size(), "index array out of sync"); | ||
|
||
const auto occurences_that_start_inside_begin_it = std::lower_bound(std::begin(this->history), std::end(this->history), prior, cmp_start_cp); | ||
const auto occurences_that_start_inside_end_it = std::upper_bound(std::begin(this->history), std::end(this->history), forth, cmp_start_pc); | ||
|
||
const auto occurencesthat_finish_inside_begin_it = std::lower_bound(std::begin(this->history_sorted_by_ends), std::end(this->history_sorted_by_ends), prior, cmp_end_ip); | ||
const auto occurencesThat_finish_inside_end_it = std::upper_bound(std::begin(this->history_sorted_by_ends), std::end(this->history_sorted_by_ends), forth, cmp_end_pi); | ||
|
||
decltype(this->history_sorted_by_ends) occurences_that_only_finish_inside; | ||
const size_t completely_inside_begin_index = std::distance(std::begin(this->history), occurences_that_start_inside_begin_it); | ||
|
||
std::copy_if(occurencesthat_finish_inside_begin_it, occurencesThat_finish_inside_end_it, std::back_inserter(occurences_that_only_finish_inside), [completely_inside_begin_index](size_t index) { return index < completely_inside_begin_index; }); | ||
std::sort(std::begin(occurences_that_only_finish_inside), std::end(occurences_that_only_finish_inside)); | ||
|
||
const auto occurences_that_start_inside_count = std::distance(occurences_that_start_inside_begin_it, occurences_that_start_inside_end_it); | ||
std::vector<OccurenceCurve> trimmed(occurences_that_start_inside_count + occurences_that_only_finish_inside.size()); | ||
|
||
using namespace std::placeholders; | ||
std::transform(occurences_that_start_inside_begin_it, occurences_that_start_inside_end_it, std::begin(trimmed), std::bind(&trim, _1, prior, forth)); | ||
std::transform(std::begin(occurences_that_only_finish_inside), std::end(occurences_that_only_finish_inside), std::begin(trimmed) + occurences_that_start_inside_count, [this, prior, forth](size_t index) { | ||
return trim(this->history[index], prior, forth); | ||
}); | ||
|
||
return trimmed; | ||
} | ||
|
||
void EntitiesConductor::converge(GameClock::moment current_time) { | ||
// TODO: perform silently all rollbacks that unapplied_history causes (simplest: jump() back, fix history, jump() forward) | ||
return; | ||
} | ||
|
||
void EntitiesConductor::append_history(const Prediction &unapplied_history) { | ||
this->unapplied_history.insert(std::end(this->unapplied_history), std::begin(unapplied_history), std::end(unapplied_history)); | ||
} | ||
|
||
}} // namespace openage::curve |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// Copyright 2015-2016 the openage authors. See copying.md for legal info. | ||
|
||
#pragma once | ||
|
||
#include <functional> | ||
#include <vector> | ||
|
||
#include "../coord/phys2.h" | ||
|
||
#include "game_clock.h" | ||
#include "occurence_curve.h" | ||
|
||
namespace openage { | ||
namespace curve { | ||
/** | ||
* Stores history, gives segments of it, can immediately mutate the world to the given moment. | ||
*/ | ||
class EntitiesConductor | ||
{ | ||
public: | ||
/** | ||
* @param emerge function to add a unit | ||
* @param vanish function to remove a unit | ||
*/ | ||
EntitiesConductor(std::function<void(int, int, coord::phys2)> emerge, std::function<void(int)> vanish); | ||
|
||
/** | ||
* Immediately mutate the world to the state of specified moment. | ||
* | ||
* @param current_time current time of the world | ||
* @param to desired time | ||
*/ | ||
void jump(GameClock::moment current_time, GameClock::moment to); | ||
|
||
/** | ||
* Output what happens between two moments. | ||
* | ||
* @param current_time current time of the world | ||
* @param other_time other point in time | ||
* @return sorted timed occurrences from the past to the future | ||
*/ | ||
Prediction predict_migration(GameClock::moment current_time, GameClock::moment other_time) const; | ||
|
||
/** | ||
* Update the history and mutate the world to eliminate the divergence. | ||
* | ||
* @param current_time current time of the world | ||
*/ | ||
void converge(GameClock::moment current_time); | ||
|
||
/** | ||
* Queue up some new history. | ||
* | ||
* It needs to be converged to take effect. | ||
* | ||
* @param list of curves | ||
*/ | ||
void append_history(const Prediction &unapplied_history); | ||
|
||
private: | ||
std::function<void(int, int, coord::phys2)> emerge; | ||
std::function<void(int)> vanish; | ||
|
||
Prediction history; | ||
std::vector<size_t> history_sorted_by_ends; | ||
Prediction unapplied_history; | ||
}; | ||
|
||
}} // namespace openage::curve |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// Copyright 2016-2016 the openage authors. See copying.md for legal info. | ||
|
||
#include "entities_conductor_interpolator.h" | ||
|
||
#include <cmath> | ||
#include <algorithm> | ||
#include <tuple> | ||
|
||
#include "../error/error.h" | ||
#include "../testing/testing.h" | ||
|
||
#include "entities_conductor.h" | ||
|
||
namespace openage { | ||
namespace curve { | ||
|
||
namespace { | ||
GameClock::moment calc_new_frame_border(GameClock::moment desired_moment, GameClock::moment behind, GameClock::moment infront, GameClock::duration min_frame_duration) { | ||
ENSURE(std::max(behind, infront) - std::min(behind, infront) >= min_frame_duration, "behind..infront must be at least min_frame_duration"); | ||
ENSURE((desired_moment < infront && infront < behind) || (behind < infront && infront <= desired_moment), "the behind..infront segment must be oriented towards the desired_moment"); | ||
|
||
const auto frame_count = decltype(min_frame_duration)(desired_moment - infront + decltype(min_frame_duration)(infront > behind ? 1 : 0)) / (1. * min_frame_duration); | ||
const auto int_frame_count = infront > behind ? std::ceil(frame_count) : std::floor(frame_count); | ||
|
||
const auto direction_unit = infront > behind ? 1 : -1; | ||
const auto delta = (decltype(min_frame_duration)::rep(int_frame_count + direction_unit)) * min_frame_duration; | ||
static_assert(std::is_signed<decltype(delta)::rep>::value, "time representation must be a signed number"); | ||
|
||
const auto new_infront = behind + delta > std::decay<decltype(behind)>::type{} ? behind + delta : std::decay<decltype(behind)>::type{}; | ||
|
||
return new_infront; | ||
} | ||
} // namespace | ||
|
||
EntitiesConductorInterpolator::EntitiesConductorInterpolator(EntitiesConductor &conductor, GameClock::duration min_frame_duration) | ||
: | ||
conductor{conductor}, | ||
min_frame_duration{min_frame_duration}, | ||
current_logic_frame_start{}, | ||
current_logic_frame_end{}, | ||
current_time{} { | ||
} | ||
|
||
void EntitiesConductorInterpolator::migrate(GameClock::moment m) { | ||
ENSURE(this->current_logic_frame_start <= this->current_logic_frame_end, "time frame bounds are messed up"); | ||
|
||
if (m >= this->current_logic_frame_end || m < this->current_logic_frame_start) | ||
this->expand_time_segment(m); | ||
|
||
this->play(m); | ||
} | ||
|
||
void EntitiesConductorInterpolator::jump(GameClock::moment m) { | ||
ENSURE(this->current_logic_frame_start <= this->current_logic_frame_end, "time frame bounds are messed up"); | ||
|
||
this->conductor.converge(this->current_time); | ||
|
||
this->conductor.jump(this->current_time, m); | ||
this->predictions_since_current_logic_frame_start.clear(); | ||
this->current_logic_frame_start = this->current_logic_frame_end = m; | ||
} | ||
|
||
void EntitiesConductorInterpolator::expand_time_segment(GameClock::moment m) { | ||
ENSURE(this->current_logic_frame_start <= this->current_logic_frame_end, "time frame bounds are messed up"); | ||
|
||
GameClock::moment *behind = nullptr; | ||
GameClock::moment *infront = nullptr; | ||
|
||
if (m >= this->current_logic_frame_end) | ||
std::tie(behind, infront) = std::make_tuple(&this->current_logic_frame_start, &this->current_logic_frame_end); | ||
else if (m < this->current_logic_frame_start) | ||
std::tie(behind, infront) = std::make_tuple(&this->current_logic_frame_end, &this->current_logic_frame_start); | ||
else | ||
ENSURE(false, "the moment is not outside of the current frame, so no frame moving needed"); | ||
|
||
std::tie(*behind, *infront) = std::make_pair(this->current_time, calc_new_frame_border(m, *behind, *infront, this->min_frame_duration)); | ||
|
||
this->conductor.converge(*behind); | ||
this->predictions_since_current_logic_frame_start = this->conductor.predict_migration(*behind, *infront); | ||
} | ||
|
||
void EntitiesConductorInterpolator::play(GameClock::moment m) { | ||
} | ||
|
||
namespace tests { | ||
void advance_frame() { | ||
calc_new_frame_border(GameClock::moment{5}, GameClock::moment{0}, GameClock::moment{5}, GameClock::duration{5}) == GameClock::moment{10} || TESTFAIL; | ||
calc_new_frame_border(GameClock::moment{10}, GameClock::moment{0}, GameClock::moment{5}, GameClock::duration{5}) == GameClock::moment{15} || TESTFAIL; | ||
calc_new_frame_border(GameClock::moment{35}, GameClock::moment{10}, GameClock::moment{30}, GameClock::duration{20}) == GameClock::moment{50} || TESTFAIL; | ||
calc_new_frame_border(GameClock::moment{58}, GameClock::moment{10}, GameClock::moment{30}, GameClock::duration{20}) == GameClock::moment{70} || TESTFAIL; | ||
calc_new_frame_border(GameClock::moment{70}, GameClock::moment{10}, GameClock::moment{30}, GameClock::duration{20}) == GameClock::moment{90} || TESTFAIL; | ||
|
||
calc_new_frame_border(GameClock::moment{29}, GameClock::moment{40}, GameClock::moment{30}, GameClock::duration{10}) == GameClock::moment{20} || TESTFAIL; | ||
calc_new_frame_border(GameClock::moment{20}, GameClock::moment{40}, GameClock::moment{30}, GameClock::duration{10}) == GameClock::moment{20} || TESTFAIL; | ||
calc_new_frame_border(GameClock::moment{5}, GameClock::moment{40}, GameClock::moment{30}, GameClock::duration{10}) == GameClock::moment{0} || TESTFAIL; | ||
} | ||
|
||
} // tests | ||
|
||
}} // namespace openage::curve |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// Copyright 2016-2016 the openage authors. See copying.md for legal info. | ||
|
||
#pragma once | ||
|
||
#include <functional> | ||
|
||
#include "game_clock.h" | ||
#include "occurence_curve.h" | ||
|
||
namespace openage { | ||
namespace curve { | ||
|
||
class EntitiesConductor; | ||
|
||
/** | ||
* Obtains segments of history and interpolates them. | ||
* | ||
* Holds the current game time. | ||
*/ | ||
class EntitiesConductorInterpolator | ||
{ | ||
public: | ||
/** | ||
* @param conductor holds the history of the world and can jump between moments in time | ||
* @param min_frame_duration minimal length of a time segment to hold | ||
*/ | ||
explicit EntitiesConductorInterpolator(EntitiesConductor &conductor, GameClock::duration min_frame_duration); | ||
|
||
/** | ||
* Move the world to the specific moment by performing everything between current time and that moment. | ||
* | ||
* Does converge when loading different segment. | ||
* | ||
* @param m target moment | ||
*/ | ||
void migrate(GameClock::moment m); | ||
|
||
/** | ||
* Immediately mutate the world to the state of specified moment. | ||
* | ||
* Does converge before jumping. | ||
* | ||
* @param m target moment | ||
*/ | ||
void jump(GameClock::moment m); | ||
|
||
private: | ||
/** | ||
* @param m target moment (must be outside of the current time segment) | ||
*/ | ||
void expand_time_segment(GameClock::moment m); | ||
|
||
void play(GameClock::moment m); | ||
|
||
EntitiesConductor &conductor; | ||
|
||
GameClock::duration min_frame_duration; | ||
|
||
/** | ||
* 'start' is included into the segment, but the 'end' is not. | ||
* 'Start' <= 'end'. | ||
*/ | ||
GameClock::moment current_logic_frame_start; | ||
GameClock::moment current_logic_frame_end; | ||
|
||
GameClock::moment current_time; | ||
|
||
Prediction predictions_since_current_logic_frame_start; | ||
}; | ||
|
||
}} // namespace openage::curve |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Copyright 2016-2016 the openage authors. See copying.md for legal info. | ||
|
||
#pragma once | ||
|
||
#include <chrono> | ||
#include <type_traits> | ||
|
||
namespace openage { | ||
namespace curve { | ||
|
||
struct GameClock { | ||
using moment = std::chrono::milliseconds; | ||
static_assert(std::is_trivially_default_constructible<moment>::value, "instance should be trivially-default-constructible since we use it everywhere"); | ||
|
||
using duration = std::chrono::milliseconds; | ||
using rep = duration::rep; | ||
using period = duration::period; | ||
using time_point = std::chrono::time_point<GameClock>; | ||
static const bool is_steady = false; | ||
}; | ||
|
||
}} // namespace openage::curve |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// Copyright 2016-2016 the openage authors. See copying.md for legal info. | ||
|
||
#pragma once | ||
|
||
namespace openage { | ||
namespace curve { | ||
|
||
class Occurence | ||
{ | ||
public: | ||
enum class kind | ||
{ | ||
ATTACK, | ||
BUILD, | ||
DIE, | ||
GARRISON, | ||
GATHER, | ||
HEAL, | ||
MOVE, | ||
PRODUCE, | ||
SPAWN, | ||
}; | ||
}; | ||
|
||
}} // namespace openage::curve |
Oops, something went wrong.