Skip to content

Commit

Permalink
net curve initial sketch
Browse files Browse the repository at this point in the history
  • Loading branch information
VelorumS committed Oct 30, 2016
1 parent ff2b625 commit 9a5a044
Show file tree
Hide file tree
Showing 11 changed files with 584 additions and 0 deletions.
1 change: 1 addition & 0 deletions libopenage/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pxdgen(
add_subdirectory("audio")
add_subdirectory("console")
add_subdirectory("coord")
add_subdirectory("curve")
add_subdirectory("cvar")
add_subdirectory("datastructure")
add_subdirectory("gui")
Expand Down
5 changes: 5 additions & 0 deletions libopenage/curve/CMakeLists.txt
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
)
68 changes: 68 additions & 0 deletions libopenage/curve/entities_conductor.cpp
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
69 changes: 69 additions & 0 deletions libopenage/curve/entities_conductor.h
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
100 changes: 100 additions & 0 deletions libopenage/curve/entities_conductor_interpolator.cpp
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
71 changes: 71 additions & 0 deletions libopenage/curve/entities_conductor_interpolator.h
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
22 changes: 22 additions & 0 deletions libopenage/curve/game_clock.h
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
25 changes: 25 additions & 0 deletions libopenage/curve/occurence.h
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
Loading

0 comments on commit 9a5a044

Please sign in to comment.