Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ApplyEffect implementation #1688

Draft
wants to merge 42 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f9fd7ce
gamestate: Apply*Effect components.
heinezen Sep 1, 2024
88a8d5f
gamestate: Create Apply*Effect components for new entities.
heinezen Sep 1, 2024
99a8431
gamestate: API interface for Apply*Effect abilities.
heinezen Sep 1, 2024
45e9523
gamestate: Fix checking ability parents.
heinezen Sep 1, 2024
5f6822e
gamestate: Add Resistance component.
heinezen Sep 7, 2024
736a60e
gamestate: Add basic system skeleton for applying effects.
heinezen Sep 7, 2024
198d848
gamestate: resistance definitions.
heinezen Sep 7, 2024
2e477cc
gamestate: API layer for nyan effects.
heinezen Sep 7, 2024
f908c9c
gamestate: API layer for nyan resistances.
heinezen Sep 7, 2024
1d825d0
gamestate: Add LineOfSight component.
heinezen Sep 7, 2024
cb31486
gamestate: LineOfSight definitions.
heinezen Sep 7, 2024
c69e0c1
gamestate: Add missing definitions for already implemented abilities.
heinezen Sep 7, 2024
01c2823
gamestate: Allow fractional values for attributes.
heinezen Sep 8, 2024
aafc6fb
gamestate: Calculate application for discrete FLAC effects.
heinezen Sep 8, 2024
ad572b4
gamestate: Decrease log level of unrecognized components.
heinezen Sep 13, 2024
d98e69f
gamestate: ApplyEffect command.
heinezen Sep 14, 2024
4b679c5
gamestate: Rename command classes and make them 'final'.
heinezen Sep 14, 2024
142240f
gamestate: Add condition for ApplyEffect command in activity system.
heinezen Sep 15, 2024
8ccd814
convert: Add new activity conditions for applying effects.
heinezen Sep 15, 2024
4e58446
gamestate: Handle ApplyEffect in activity system.
heinezen Sep 15, 2024
ced1ee0
gamestate: Fix time calculations for applying effects.
heinezen Sep 15, 2024
fee671d
gamestate: Move animation property handling to helper function.
heinezen Sep 15, 2024
d508a5b
gamestate: Animate effect application.
heinezen Sep 15, 2024
27ad344
curve: Add compress argument for curve operations.
heinezen Oct 16, 2024
2e4c568
curve: Rename argument for keyframes to 'keyframe'.
heinezen Oct 16, 2024
7b23eec
curve: Compress operation on keyframe insertion.
heinezen Oct 16, 2024
486d6df
curve: Compress method for curves.
heinezen Oct 18, 2024
0c071ba
curve: Add new unit tests for compress() method.
heinezen Oct 18, 2024
b3df48a
curve: Fix compression method.
heinezen Oct 18, 2024
bbb697f
curve: Pass through compression args.
heinezen Oct 19, 2024
ba2c3ad
curve: Compress during curve sync.
heinezen Oct 19, 2024
c017074
renderer: Compress sync on animations curve.
heinezen Oct 19, 2024
6073e25
renderer: Make fetching from render entity more reliable.
heinezen Oct 20, 2024
ae6b8c3
curve: Fix compilation for oider clang versions.
heinezen Oct 20, 2024
ce7242d
curve: Concept for curve values.
heinezen Oct 20, 2024
87e7ebe
doc: Add documentation for curve compression.
heinezen Oct 22, 2024
4444284
gamestate: Add activity node type for branching on value.
heinezen Nov 4, 2024
9878f65
gamestate: Handle XorSwichGate in activity system.
heinezen Nov 5, 2024
a8709de
gamestate: Add unit tests for activity node types.
heinezen Nov 5, 2024
0253022
gamestate: Make a lookup function for next comand switching.
heinezen Nov 5, 2024
754ad80
convert: Use new switch gate for command branching.
heinezen Nov 5, 2024
63bc382
gamestate: Init new XorSwitchGate activity node type from nyan.
heinezen Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions doc/code/curves.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Curves are an integral part of openage's event-based game simulation.
2. [Container](#container)
1. [Queue](#queue)
2. [Unordered Map](#unordered-map)
4. [Compression](#compression)


## Motivation
Expand Down Expand Up @@ -132,6 +133,9 @@ Modify operations insert values for a specific point in time.
| `set_insert(t, value)` | Insert a new keyframe value at time `t` |
| `set_last(t, value)` | Insert a new keyframe value at time `t`; delete all keyframes after time `t` |
| `set_replace(t, value)` | Insert a new keyframe value at time `t`; remove all other keyframes with time `t` |
| `compress(t)` | Remove redundant keyframes at and after time `t`; see [Compression] for more info |

[Compression]: #compression

**Copy**

Expand Down Expand Up @@ -253,3 +257,27 @@ Unordered map curve containers store key-value pairs while additionally keeping
track of element insertion time. Requests for a key `k` at time `t` will return the value
of `k` at that time. The unordered map can also be iterated over for a specific time `t` which
allows access to all key-value pairs that were in the map at time `t`.

## Compression

Curves support basic lossless compression by removing redundant keyframes from the curve.
Keyframes are considered redundant if they do not change any interpolation results, i.e.
the result of `get(t)` does not change.

The most straight-forward way to use compression with primitive curves is the `compress(t)`
method. `compress(t)` iterates over the curve and removes all redundant keyframes after
or at time `t`. The runtime has linear complexity `O(n)` based on the number of elements
in the keyframe container.

Furthermore, primitive curves support incremental compression during insertion for the
`set_insert(t, value)` and `set_last(t, value)` methods via their `compress` argument.
If compression is active, `(t, value)` is only inserted when it is not a redundant
keyframe. `sync(Curve, t)` also supports compression with a flag `compress` passed as
an argument.

Compression may be used in cases where the size should be kept small, e.g. when the curve
is tranferred via network or recorded in a replay file. Another application of compression
is in the [renderer](/doc/code/renderer/README.md) for the discrete curves storing an object's
animations. Since compression removes redundant animation entries, the renderer can determine
when the current animation has started much easier as this is then returned by the keyframe
time in `frame(t)`.
17 changes: 9 additions & 8 deletions doc/code/game_simulation/activity.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,12 @@ you can use available [BPMN tools](https://bpmn.io/) to draw activity node graph
## Node Types


| Type | Inputs | Outputs | Description |
| ---------------- | ------ | ------- | ------------------------- |
| `START` | 0 | 1 | Start of activity |
| `END` | 1 | 0 | End of activity |
| `TASK_SYSTEM` | 1 | 1 | Run built-in system |
| `TASK_CUSTOM` | 1 | 1 | Run custom function |
| `XOR_EVENT_GATE` | 1 | 1+ | Wait for event and branch |
| `XOR_GATE` | 1 | 1+ | Branch on condition |
| Type | Inputs | Outputs | Description |
| ----------------- | ------ | ------- | ------------------------- |
| `START` | 0 | 1 | Start of activity |
| `END` | 1 | 0 | End of activity |
| `TASK_SYSTEM` | 1 | 1 | Run built-in system |
| `TASK_CUSTOM` | 1 | 1 | Run custom function |
| `XOR_EVENT_GATE` | 1 | 1+ | Wait for event and branch |
| `XOR_GATE` | 1 | 1+ | Branch on condition |
| `XOR_SWITCH_GATE` | 1 | 1+ | Branch on value |
1 change: 1 addition & 0 deletions libopenage/curve/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
add_sources(libopenage
base_curve.cpp
concept.cpp
continuous.cpp
discrete.cpp
discrete_mod.cpp
Expand Down
117 changes: 94 additions & 23 deletions libopenage/curve/base_curve.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#pragma once

#include <concepts>
#include <cstddef>
#include <functional>
#include <memory>
Expand All @@ -13,6 +14,7 @@
#include "log/log.h"
#include "log/message.h"

#include "curve/concept.h"
#include "curve/keyframe_container.h"
#include "event/evententity.h"
#include "time/time.h"
Expand All @@ -26,7 +28,7 @@ class EventLoop;

namespace curve {

template <typename T>
template <KeyframeValueLike T>
class BaseCurve : public event::EventEntity {
public:
BaseCurve(const std::shared_ptr<event::EventLoop> &loop,
Expand Down Expand Up @@ -73,30 +75,62 @@ class BaseCurve : public event::EventEntity {
/**
* Insert/overwrite given value at given time and erase all elements
* that follow at a later time.
*
* If multiple elements exist at the given time,
* overwrite the last one.
*
* @param at Time the keyframe is inserted at.
* @param value Value of the keyframe.
* @param compress If true, only insert the keyframe if the value at time \p at
* is different from the given value.
*/
virtual void set_last(const time::time_t &at, const T &value);
virtual void set_last(const time::time_t &at,
const T &value,
bool compress = false);

/**
* Insert a value at the given time.
*
* If there already is a value at this time,
* the value is inserted directly after the existing one.
*
* @param at Time the keyframe is inserted at.
* @param value Value of the keyframe.
* @param compress If true, only insert the keyframe if the value at time \p at
* is different from the given value.
*/
virtual void set_insert(const time::time_t &at, const T &value);
virtual void set_insert(const time::time_t &at,
const T &value,
bool compress = false);

/**
* Insert a value at the given time.
*
* If there already is a value at this time,
* the given value will replace the first value with the same time.
*
* @param at Time the keyframe is inserted at.
* @param value Value of the keyframe.
*/
virtual void set_replace(const time::time_t &at, const T &value);
virtual void set_replace(const time::time_t &at,
const T &value);

/**
* Remove all values that have the given time.
*/
virtual void erase(const time::time_t &at);

/**
* Compress the curve by removing redundant keyframes.
*
* A keyframe is redundant if it doesn't change the value calculation of the curve
* at any given time, e.g. duplicate keyframes.
*
* @param start Start time at which keyframes are compressed (default = -INF).
* Using the default value compresses ALL keyframes of the curve.
*/
virtual void compress(const time::time_t &start = time::TIME_MIN) = 0;

/**
* Integrity check, for debugging/testing reasons only.
*/
Expand All @@ -112,9 +146,13 @@ class BaseCurve : public event::EventEntity {
* @param start Start time at which keyframes are replaced (default = -INF).
* Using the default value replaces ALL keyframes of \p this with
* the keyframes of \p other.
* @param compress If true, redundant keyframes are not copied during the sync.
* Redundant keyframes are keyframes that don't change the value
* calculaton of the curve at any given time, e.g. duplicate keyframes.
*/
void sync(const BaseCurve<T> &other,
const time::time_t &start = time::TIME_MIN);
const time::time_t &start = time::TIME_MIN,
bool compress = false);

/**
* Copy keyframes from another curve (with a different element type) to this curve.
Expand All @@ -129,11 +167,15 @@ class BaseCurve : public event::EventEntity {
* @param start Start time at which keyframes are replaced (default = -INF).
* Using the default value replaces ALL keyframes of \p this with
* the keyframes of \p other.
* @param compress If true, redundant keyframes are not copied during the sync.
* Redundant keyframes are keyframes that don't change the value
* calculaton of the curve at any given time, e.g. duplicate keyframes.
*/
template <typename O>
template <KeyframeValueLike O>
void sync(const BaseCurve<O> &other,
const std::function<T(const O &)> &converter,
const time::time_t &start = time::TIME_MIN);
const time::time_t &start = time::TIME_MIN,
bool compress = false);

/**
* Get the identifier of this curve.
Expand Down Expand Up @@ -198,8 +240,10 @@ class BaseCurve : public event::EventEntity {
};


template <typename T>
void BaseCurve<T>::set_last(const time::time_t &at, const T &value) {
template <KeyframeValueLike T>
void BaseCurve<T>::set_last(const time::time_t &at,
const T &value,
bool compress) {
auto hint = this->container.last(at, this->last_element);

// erase max one same-time value
Expand All @@ -209,55 +253,72 @@ void BaseCurve<T>::set_last(const time::time_t &at, const T &value) {

hint = this->container.erase_after(hint);

if (compress and this->get(at) == value) {
// skip insertion if the value is the same as the last one
// erasure still happened, so we need to notify about the change
this->changes(at);
return;
}

this->container.insert_before(at, value, hint);
this->last_element = hint;

this->changes(at);
}


template <typename T>
void BaseCurve<T>::set_insert(const time::time_t &at, const T &value) {
template <KeyframeValueLike T>
void BaseCurve<T>::set_insert(const time::time_t &at,
const T &value,
bool compress) {
if (compress and this->get(at) == value) {
// skip insertion if the value is the same as the last one
return;
}

auto hint = this->container.insert_after(at, value, this->last_element);

// check if this is now the final keyframe
if (this->container.get(hint).time() > this->container.get(this->last_element).time()) {
this->last_element = hint;
}

this->changes(at);
}


template <typename T>
void BaseCurve<T>::set_replace(const time::time_t &at, const T &value) {
template <KeyframeValueLike T>
void BaseCurve<T>::set_replace(const time::time_t &at,
const T &value) {
this->container.insert_overwrite(at, value, this->last_element);
this->changes(at);
}


template <typename T>
template <KeyframeValueLike T>
void BaseCurve<T>::erase(const time::time_t &at) {
this->last_element = this->container.erase(at, this->last_element);
this->changes(at);
}


template <typename T>
template <KeyframeValueLike T>
std::pair<time::time_t, const T> BaseCurve<T>::frame(const time::time_t &time) const {
auto e = this->container.last(time, this->container.size());
auto elem = this->container.get(e);
return std::make_pair(elem.time(), elem.val());
}


template <typename T>
template <KeyframeValueLike T>
std::pair<time::time_t, const T> BaseCurve<T>::next_frame(const time::time_t &time) const {
auto e = this->container.last(time, this->container.size());
e++;
auto elem = this->container.get(e);
return std::make_pair(elem.time(), elem.val());
}

template <typename T>
template <KeyframeValueLike T>
std::string BaseCurve<T>::str() const {
std::stringstream ss;
ss << "Curve[" << this->idstr() << "]{" << std::endl;
Expand All @@ -269,7 +330,7 @@ std::string BaseCurve<T>::str() const {
return ss.str();
}

template <typename T>
template <KeyframeValueLike T>
void BaseCurve<T>::check_integrity() const {
time::time_t last_time = time::TIME_MIN;
for (const auto &keyframe : this->container) {
Expand All @@ -280,9 +341,10 @@ void BaseCurve<T>::check_integrity() const {
}
}

template <typename T>
template <KeyframeValueLike T>
void BaseCurve<T>::sync(const BaseCurve<T> &other,
const time::time_t &start) {
const time::time_t &start,
bool compress) {
// Copy keyframes between containers for t >= start
this->last_element = this->container.sync(other.container, start);

Expand All @@ -293,15 +355,20 @@ void BaseCurve<T>::sync(const BaseCurve<T> &other,
this->set_insert(start, get_other);
}

if (compress) {
this->compress(start);
}

this->changes(start);
}


template <typename T>
template <typename O>
template <KeyframeValueLike T>
template <KeyframeValueLike O>
void BaseCurve<T>::sync(const BaseCurve<O> &other,
const std::function<T(const O &)> &converter,
const time::time_t &start) {
const time::time_t &start,
bool compress) {
// Copy keyframes between containers for t >= start
this->last_element = this->container.sync(other.get_container(), converter, start);

Expand All @@ -312,6 +379,10 @@ void BaseCurve<T>::sync(const BaseCurve<O> &other,
this->set_insert(start, get_other);
}

if (compress) {
this->compress(start);
}

this->changes(start);
}

Expand Down
9 changes: 9 additions & 0 deletions libopenage/curve/concept.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright 2024-2024 the openage authors. See copying.md for legal info.

#include "concept.h"


namespace openage::curve {


} // namespace openage::curve
15 changes: 15 additions & 0 deletions libopenage/curve/concept.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2024-2024 the openage authors. See copying.md for legal info.

#pragma once

#include <concepts>

namespace openage::curve {

/**
* Concept for keyframe values.
*/
template <typename T>
concept KeyframeValueLike = std::copyable<T> && std::equality_comparable<T>;

} // namespace openage::curve
Loading
Loading