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
6569c20
gamestate: Apply*Effect components.
heinezen Sep 1, 2024
14b25f6
gamestate: Create Apply*Effect components for new entities.
heinezen Sep 1, 2024
b1737a0
gamestate: API interface for Apply*Effect abilities.
heinezen Sep 1, 2024
e0104da
gamestate: Fix checking ability parents.
heinezen Sep 1, 2024
ff2f03e
gamestate: Add Resistance component.
heinezen Sep 7, 2024
58dbeca
gamestate: Add basic system skeleton for applying effects.
heinezen Sep 7, 2024
413fda2
gamestate: resistance definitions.
heinezen Sep 7, 2024
d38422d
gamestate: API layer for nyan effects.
heinezen Sep 7, 2024
1bbcdeb
gamestate: API layer for nyan resistances.
heinezen Sep 7, 2024
2741005
gamestate: Add LineOfSight component.
heinezen Sep 7, 2024
3a72766
gamestate: LineOfSight definitions.
heinezen Sep 7, 2024
698cdb9
gamestate: Add missing definitions for already implemented abilities.
heinezen Sep 7, 2024
18d6899
gamestate: Allow fractional values for attributes.
heinezen Sep 8, 2024
b3459a5
gamestate: Calculate application for discrete FLAC effects.
heinezen Sep 8, 2024
9d0bdce
gamestate: Decrease log level of unrecognized components.
heinezen Sep 13, 2024
be7a50f
gamestate: ApplyEffect command.
heinezen Sep 14, 2024
92d9511
gamestate: Rename command classes and make them 'final'.
heinezen Sep 14, 2024
5df0e97
gamestate: Add condition for ApplyEffect command in activity system.
heinezen Sep 15, 2024
60a4682
convert: Add new activity conditions for applying effects.
heinezen Sep 15, 2024
eda6a6a
gamestate: Handle ApplyEffect in activity system.
heinezen Sep 15, 2024
70a19c8
gamestate: Fix time calculations for applying effects.
heinezen Sep 15, 2024
5bbd331
gamestate: Move animation property handling to helper function.
heinezen Sep 15, 2024
4d1ae85
gamestate: Animate effect application.
heinezen Sep 15, 2024
9c703d5
curve: Add compress argument for curve operations.
heinezen Oct 16, 2024
30840eb
curve: Rename argument for keyframes to 'keyframe'.
heinezen Oct 16, 2024
1eed0c6
curve: Compress operation on keyframe insertion.
heinezen Oct 16, 2024
1610126
curve: Compress method for curves.
heinezen Oct 18, 2024
23c808e
curve: Add new unit tests for compress() method.
heinezen Oct 18, 2024
967a74f
curve: Fix compression method.
heinezen Oct 18, 2024
34c5aaf
curve: Pass through compression args.
heinezen Oct 19, 2024
cc48ec3
curve: Compress during curve sync.
heinezen Oct 19, 2024
3597482
renderer: Compress sync on animations curve.
heinezen Oct 19, 2024
a998c44
renderer: Make fetching from render entity more reliable.
heinezen Oct 20, 2024
90d8bd0
curve: Fix compilation for oider clang versions.
heinezen Oct 20, 2024
9196763
curve: Concept for curve values.
heinezen Oct 20, 2024
8c80dbd
doc: Add documentation for curve compression.
heinezen Oct 22, 2024
ef90cea
gamestate: Add activity node type for branching on value.
heinezen Nov 4, 2024
a3b1c78
gamestate: Handle XorSwichGate in activity system.
heinezen Nov 5, 2024
6e62921
gamestate: Add unit tests for activity node types.
heinezen Nov 5, 2024
0d3cfdc
gamestate: Make a lookup function for next comand switching.
heinezen Nov 5, 2024
759b944
convert: Use new switch gate for command branching.
heinezen Nov 5, 2024
35d4f37
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