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

Event logic #744

Closed
wants to merge 24 commits into from
Closed

Event logic #744

wants to merge 24 commits into from

Conversation

Tomatower
Copy link
Contributor

@Tomatower Tomatower commented Feb 18, 2017

The tube demo runs a simple curses based pong, that shall
demonstrate the functionality where and why a prediction interface is
built. 🤢

@TheJJ TheJJ added lang: c++ Done in C++ code nice new thing ☺ A new feature that was not there before labels Feb 18, 2017
@VelorumS
Copy link
Contributor

VelorumS commented Mar 4, 2017

Maybe use std::chrono for time? And the linked list structure is just std::list (or can use a std::vector with binary_search/upper_bound/lower_bound). It's easier to explain the concept if there are less of home-made versions of trivial stuff.

@Tomatower
Copy link
Contributor Author

You got a point there.
I have rebuild everything on top of std::list.

I have tried to condense the gamestate down into the most basic datatypes that need to be timely tracked.
Can we discuss, that I did not forget any important data structures?
I also have attached, where I would use which types to make it more clear what I am trying to say.

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 ...

https://github.com/SFTtech/openage/blob/master/libopenage/gamestate/player.h#L139
https://github.com/SFTtech/openage/blob/master/libopenage/gamestate/game_main.h#L92
https://github.com/SFTtech/openage/blob/master/libopenage/gamestate/player.h#L133
https://github.com/SFTtech/openage/blob/master/libopenage/gamestate/team.h#L56
https://github.com/SFTtech/openage/blob/master/libopenage/unit/attributes.h#L61
https://github.com/SFTtech/openage/blob/master/libopenage/unit/unit.h#L261

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.

https://github.com/SFTtech/openage/blob/master/libopenage/pathfinding/path.h#L189
https://github.com/SFTtech/openage/blob/master/libopenage/unit/unit.h#L267

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, ...

https://github.com/SFTtech/openage/blob/master/libopenage/gamestate/population_tracker.h#L85
https://github.com/SFTtech/openage/blob/master/libopenage/unit/attribute.h#L215

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, ...

https://github.com/SFTtech/openage/blob/master/libopenage/unit/unit.h#L68

Nyan Values

This container keeps track of nyan objects over time and their respective properties.

  • Usage of this will be there one day

@TheJJ TheJJ mentioned this pull request Apr 5, 2017
7 tasks
@Tomatower Tomatower changed the title Tubes: tubes logic and epic tubepong [WIP] Tubes: tubes logic and epic tubepong Apr 14, 2017
@Tomatower Tomatower changed the title [WIP] Tubes: tubes logic and epic tubepong [WIP] Tubes logic Apr 14, 2017
@Tomatower Tomatower force-pushed the tubes branch 8 times, most recently from 2d8891b to f2bd2ab Compare April 17, 2017 21:31
@MaanooAk
Copy link
Contributor

MaanooAk commented Aug 30, 2017

I will put my thoughts on curves here

  • They are really hard

  • At the moment you have a Curve from the start of time till the end of time (assertion check). I think that wrappers of Curves should be used. Examples:

    • Array of curves: wraps a list of curves (one after the other)
    • Repeating curve: a looping curve (which can be a Array of curves)

Example:
A villager mining:

Position of the villager:
Repeating curve { Array of curves { Path Curve, Const Curve, Path Curve } }
Resources of the villager:
Repeating curve { Array of curves { Const Curve, Linear Curve, Const Curve} }

Note: The wrappers would also implement the Curve interface

  • Reduce the events to minimal:
    • Why: events disrupt the predictability (= more updates to the curves (= more traffic))
    • How: use dummy curves to fill the duration of the delay

Example:
Instead of timer triggering an event, use a

Array of curves { Const Curve, Const Curve }

where the first const curve has the old value with a duration of the timer and the second curve the new value with infinite duration

Note: @TheJJ So the IntervalTimer would not become a event emitter but it would just be removed

  • Delete the no longer needed key frames from the memory (write to replay file if needed)

  • Hide everything: the use of curve should become as simple and pure:

Example:
from:

int hitpoints
hitpoints = 10
...
hitpoints += 2 * delta

to:

IntLinearCurve hitpoints = new IntLinearCurve()
hitpoints[now] = 10
...
hitpoints[now + (50 - hitpoint[now]) ] = 100

Side note to @TheJJ:
You have to select one of the big projects (nyan or tubes) and make branch in order to start, it will take months to implement either and it cant be incremental (I suggest first nyan because it has an impact of the actual game, curves will be hidden and they far away from using)

@Tomatower
Copy link
Contributor Author

I agree on the thing, that curves are hard, but actually we have a working prototype.

The curves are very tightly entangled with the event execution pipeline. The events are basically the future part of the curves, executed to extend the validity of curves.

Curves are just another normal datatype, you can put some curves into a struct, and you can put structs into a curve.

// Curve in a Struct
struct thing {
    curve::Continuous<int> hitpoints;
    int id;
};

// Struct in a curve
// It is not possible to use a continuous curve here - would need + and * for interpolation.
struct parameters {
    int a;
    int b;
};
curve::Discrete<parameters> params;

It is only very confusing to use a curve in a curve, because every new keyframe would store a new curve for the inner curve, dropping the history of the contained curve.

Repeating curves can be implemented using callbacks at the end of a loop, reinserting values again.
I did not see the benefit for repeating curves compared to the overhead it would impose.

Your example would be in the current API:

auto hitpoints = std::make_shared<curve::Continuous<int>>(); // If a think wants to be targeted by an event it has to be a `std::shared_ptr`;                                                                  
hitpoints.set_drop(now, 10); // set as new last value, ignoring all previous history
...
time_until_damage = 30; // time between attacks of a unit (sword-strikes) in ms
damage = 1; // the damage the unit takes on the attack
hitpoints.set_drop(now + time_until_damage, hitpoints.get(now) - damage);

To make sure, that multiple units can damage another unit, it would be better to implement this damage by an event "target_hit" as a once-event, at time of hitting the unit.

This would lead to a little bit more code, which shall be part of the standard library:

// The Damage Event, that has to be set up in order to register an event.
class EventDamage : public curve::EventClass {
public:
    EventDamage() : curve::EventClass("openage.damage", EventClass::Type::ONCE) {}
   // The Damage Event does not depend (for re-prediction) on any other values
    void setup(const std::shared_ptr<Event> &evnt,
	           const std::shared_ptr<State> &state) override {
         // if it would depend, we have to add dependencies:
         //this->add_dependency(evnt, evnt->target);
    }
    // Whenever the event is up for execution do this. It is guaranteed, that all events up to this point
    // have been executed - but it is not guarenteed, that it will be executed only once!
    void call(curve::EventManager *mgr,
	          const std::shared_ptr<EventTarget> &target,
	          const std::shared_ptr<State> &state,
	          const curve::curve_time_t &now) override {
        // Do the actual logic here
        target->hitpoints->set_drop(target->hitpoints->get(now) - 1);
    }
};
...
// Add the event class to the pool of events - this is done during startup.
EventManager mgr;
mgr.add_class(std::make_shared<EventDamage>());

Now it can be used from the game logic.

// This connection between objects and events can be derived from defaults, abilities, or nyan
// Unit has to implement the EventTarget interface, or be a curve.
mgr.on("openage.damage", unit);

Currently I am still working on a nice interface to add additional parameters to the mgr.on call - in this case for example the amount of damage this event will do.

Still the question, what would be better - va_args, std::map<std::string, std::any>, void *, or template the shit out of it?

@TheJJ TheJJ changed the title [WIP] Tubes logic [WIP] Event logic Aug 31, 2017
@TheJJ TheJJ added this to the Event-driven game logic milestone Dec 3, 2017
The tube demo runs a simple curses based pong, that shall
demonstrate the functionality where and why a prediction interface is
built.
@TheJJ TheJJ changed the title [WIP] Event logic Event logic Dec 10, 2017
@@ -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`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

libpng is still needed. and the other distros need the ncurses package as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

libncurses should be an optional dependency, like it is done for inotify.

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.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover, what do? probably crash :)

@@ -0,0 +1,120 @@
TUBE DATATYPES
=================
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls move this file to $root/doc/code/

| 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 |
+-----------------------------------------------------------+
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is broken. shall it be a table or a <pre>?

Meaning of Flags
----------------

== DELETE ==
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those headlines just show up as == DELETE ==, maybe you meant ## DELETE ##? otherwise, maybe ``?

mgr->on("demo.ball.reflect_panel", state->ball->position, state, now);
// FIXME once "reset": deregister

// reset(state, mgr, now);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover

// state->ball.position->set_drop(now, state.resolution * 0.5);
//}
//update_ball(state, now, init_recursion_limit);
//break;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

many leftovers

Event Queue Magic
=====================

The Event Queue consists of Events and their Context:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also move to doc/code/ pls


The Event Queue consists of Events and their Context:

```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to enhance the highlighting, c++ could improve it


Internas
=========

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

either leave out of fill with content :)

@TheJJ TheJJ self-assigned this Jan 9, 2018
@zuntrax zuntrax added the big stuff High-impact changes, mainly foundation work label Jan 21, 2018
* cppclass Continuous(ValueContainer):
* _T get(const curve_time_t&) except +
*/
template<typename _T>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should agree on a project wide style for naming template parameters. _T is new to me, maybe we could do that everywhere. Needs discussion.

namespace openage {
namespace curvepong {

std::vector<event> &Gui::getInputs(const std::shared_ptr<PongPlayer> &player) {
Copy link
Member

@TheJJ TheJJ Jan 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

camelcase getInputs -> get_inputs

COLOR_2 = 7,
COLOR_3 = 8,
COLOR_4 = 9,
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not important, but could be a enum class and you then access with color::player1 etc.

curvepong::Gui gui;
#endif
bool running = true;
srand(time(NULL));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have our util/random system that should be used instead

#elif REALTIME == 2
now += 4;
#else
#error no REALTIME plan set
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indent

auto positioncurve = std::dynamic_pointer_cast<Continuous<util::Vector<2>>>(target);
auto speedcurve = state->ball->speed;

// All the magic in the next lines
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls explain the magic

* Every Object in the gameworld that wants to be targeted by events or as
* dependency for events, has to implement this class.
*/
class EventTarget {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would Entity be a better name? Or would Entity be the "main" class that inherits from EventTarget?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only used to interface with the event system. I would have a core Game Entity, that inherits this event interface

add_sources(libopenage
keyframe_container.cpp
value_container.cpp
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can rename the internal folder to data_container because that's what's in the folder?

public:
/**
* A element of the curvecontainer. This is especially used to keep track of
* the value-timing.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indent


// 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<curve_time_t>::min()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indent and leftover

@@ -0,0 +1,111 @@
// Copyright 2017-2017 the openage authors. See copying.md for legal info.

#pragma once
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we added this file as a prototype but we should remove this from the pull request to then work on its creation in #964

@TheJJ
Copy link
Member

TheJJ commented Jun 3, 2018

#1008 obsoletes this one.

@TheJJ TheJJ closed this Jun 3, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
big stuff High-impact changes, mainly foundation work lang: c++ Done in C++ code nice new thing ☺ A new feature that was not there before
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants