Zobrazit předchozí téma :: Zobrazit následující téma |
Autor |
Zpráva |
mar
Založen: 16. 06. 2012 Příspěvky: 608
|
Zaslal: 20. březen 2015, 04:40:47 Předmět: "coroutines" v C++ (latent state) |
|
|
Možná se to tak běžně řeší, ale přijde mi to celkem fajn (hodlám skriptovat v C++).
Vytažená (+lehce upravená a zjednodušená) část z mého nového frameworku (in progress), možná by se to někomu mohlo hodit.
Stav:
kód: |
struct LatentState
{
int state; // current state index
float togo; // because of wait
inline LatentState() : state(0), togo(0) {}
inline void Reset( Float delta = 0 ) {
state = 0;
togo = -delta;
}
};
|
Makra: (EDIT: upraveno pro konzistenci při větších krocích a zjednodušení)
kód: |
#define LATENT_FUNC(f) LatentState state_##f##_
#define LATENT_START(f, delta) \
int Llatent_counter_ = 0; \
float Llatent_delta_ = (delta); \
LatentState &Llatent_state_ = state_##f##_; \
if ( Llatent_state_.state == Llatent_counter_ ) {
#define LATENT_END() \
++Llatent_state_.state; \
} \
if ( Llatent_state_.state < ++Llatent_counter_) { \
return; \
}
#define LATENT_WAIT(w) \
++Llatent_state_.state; \
Llatent_state_.togo += (w); \
} \
if ( Llatent_state_.state == ++Llatent_counter_ ) { \
if ( (Llatent_state_.togo -= Llatent_delta_) > 0 ) { \
return; \
} \
Llatent_delta_ = 0;
#define LATENT_WAIT_TRUE(expr) \
++Llatent_state_.state; \
} \
if ( Llatent_state_.state == ++Llatent_counter_ ) { \
if (!(expr)) { \
Llatent_state_.togo = Min( Llatent_state_.togo + Llatent_delta_, 0.0f ); \
return; \
}
#define LATENT_WAIT_FALSE(expr) \
++Llatent_state_.state; \
} \
if ( Llatent_state_.state == ++Llatent_counter_ ) { \
if (expr) { \
Llatent_state_.togo = Min( Llatent_state_.togo + Llatent_delta_, 0.0f ); \
return; \
}
#define LATENT_FADE_IN(w, var) \
++Llatent_state_.state; \
Llatent_state_.togo += (w); \
} \
if ( Llatent_state_.state == ++Llatent_counter_ ) { \
Llatent_state_.togo -= Llatent_delta_; \
LLatent_delta_ = 0; \
(var) = 1.0f - Max( Llatent_state_.togo / (w), 0.0f );
#define LATENT_FADE_OUT(w, var) \
++Llatent_state_.state; \
Llatent_state_.togo += (w); \
} \
if ( Llatent_state_.state == ++Llatent_counter_ ) { \
Llatent_state_.togo -= Llatent_delta_; \
LLatent_delta_ = 0; \
(var) = Max( Llatent_state_.togo / (w), 0.0f );
#define LATENT_FADE_END() \
if ( Llatent_state_.togo > 0 ) { \
return; \
}
#define LATENT_RESET(f) do { state_##f##_.Reset(); } while(0)
#define LATENT_RESET_DELTA(f, delta) do { state_##f##_.Reset(delta); } while(0)
|
Použití:
kód: |
// header
class Test
{
public:
void TestLatent( float delta );
LATENT_FUNC(TestLatent);
};
// cpp
void Test::TestLatent( float delta )
{
float fade;
LATENT_START(TestLatent, delta)
Log("TestLatent: initializing...");
PlaySound("Sounds/init.wav");
LATENT_WAIT(2.0f)
PlaySound("Sounds/click.wav");
Log("TestLatent: after 2 sec wait...");
LATENT_WAIT(1.0f)
PlaySound("Sounds/click.wav");
Log("TestLatent: after another 1 sec wait...");
LATENT_WAIT(0.5f)
LATENT_FADE_IN(0.5f, fade)
// fade = normalized (0.0..1.0)
Log("Fade: %0.6f", fade);
LATENT_FADE_END()
LATENT_FADE_OUT(0.5f, fade)
Log("Fade: %0.6f", fade);
LATENT_FADE_END()
Log("TestLatent: finished");
LATENT_END()
// this gets called repeatedly when latent part finishes
LATENT_RESET_DELTA(TestLatent, delta);
}
|
Myslím, že by se to dalo časem rozšířit i o další zajímavé waity (třeba na dokončení nějakého eventu atd.), vlastně jsem to teď přidal.
LATENT_WAIT_TRUE(expr) čeká, až je výraz true (WAIT_FALSE naopak), tj. dá se takhle pollovat nějaký stav.
Co se týká výkonu, jsou to jednoduché ify. Switch by byl lepší, ale nejsem schopný generovat unique idčka bez hintu ze strany uživatele, myslím že i tak ok.
Další výhoda je možnost jednoduché serializace (proto jsem použil dt).
Samozřejmě nevýhoda oproti řešením ve skriptech, kde si pamatuje i lokální stack je ta, že lokální proměnné nepřežijí do dalšího volání, ale to je myslím jasné.
Naposledy upravil mar dne 20. březen 2015, 15:10:16, celkově upraveno 6 krát |
|
Návrat nahoru |
|
|
perry
Založen: 28. 07. 2009 Příspěvky: 879
|
Zaslal: 20. březen 2015, 09:11:47 Předmět: |
|
|
Možná se zeptám hodně blbě, ale jak funguje např. to wait? Nebo podle čeho to vlastně čeká... _________________ Perry.cz |
|
Návrat nahoru |
|
|
mar
Založen: 16. 06. 2012 Příspěvky: 608
|
Zaslal: 20. březen 2015, 09:23:48 Předmět: |
|
|
perry napsal: |
Možná se zeptám hodně blbě, ale jak funguje např. to wait? Nebo podle čeho to vlastně čeká... |
No je to v podstatě kooperativní multitasking.
Uvnitř objektu máš proměnnou (LatentState), která drží id stavu a float zbýv. čas waitu.
Funguje to tak, že každý frame zavoláš na ten objekt TestLatent( dt ), v podstatě update.
dt je delta čas od předch. framu, takže ten wait potom točí a odečítá delta a až pak to pustí dál.
Takže to je v podstatě velmi jednoduchá state machine a probublá to přes ty ify.
Takže pokud to nakrmíš reálným časem (deltou), tak WAIT(2.0f) počká 2 vteřiny, než se vyvolá další kód. |
|
Návrat nahoru |
|
|
perry
Založen: 28. 07. 2009 Příspěvky: 879
|
Zaslal: 20. březen 2015, 10:39:38 Předmět: |
|
|
Jo jasně.. právě to s tím voláním v každém framu jsem v tom neviděl a nenapadlo mě to _________________ Perry.cz |
|
Návrat nahoru |
|
|
mar
Založen: 16. 06. 2012 Příspěvky: 608
|
Zaslal: 23. březen 2015, 10:40:44 Předmět: Re: "coroutines" v C++ (latent state) |
|
|
Takže naštěstí všechny mainstreamové překladače (msc, gcc, clang) podporují __COUNTER__ (což bohužel není ve standardu, stejně jako #pragma once;
mimochodem standardizovat toto by bylo IMHO mnohem užitečnější, než vymýšlet nové super cool featury do C++17),
takže je možné to naimplementovat pomocí switche - tj. compare max, cond. jump a jumptable)
Tím pádem sebesložitější struktura (waity) bude mít prakticky nulový overhead.
kód: |
#define LATENT_FUNC(f) LatentState state_##f##_
#define LATENT_FADE_END() \
if ( Llatent_state_.togo > 0 ) { \
return; \
}
#define LATENT_START(f, delta) \
float Llatent_delta_ = (delta); \
LatentState &Llatent_state_ = state_##f##_; \
const int Llatent_base_ = __COUNTER__; \
switch ( Llatent_state_.state ) { \
case Llatent_base_: {
#define LATENT_END() \
++Llatent_state_.state; \
} \
}
#define LATENT_WAIT(w) \
++Llatent_state_.state; \
Llatent_state_.togo += (w); \
} \
case __COUNTER__ - Llatent_base_: \
{ \
if ( (Llatent_state_.togo -= Llatent_delta_) > 0 ) { \
return; \
} \
Llatent_delta_ = 0;
#define LATENT_WAIT_TRUE(expr) \
++Llatent_state_.state; \
} \
case __COUNTER__ - Llatent_base_: \
{ \
if (!(expr)) { \
Llatent_state_.togo = Min( Llatent_state_.togo + Llatent_delta_, 0.0f ); \
return; \
}
#define LATENT_WAIT_FALSE(expr) \
++Llatent_state_.state; \
} \
case __COUNTER__ - Llatent_base_: \
{ \
if (expr) { \
Llatent_state_.togo = Min( Llatent_state_.togo + Llatent_delta_, 0.0f ); \
return; \
}
#define LATENT_FADE_IN(w, var) \
++Llatent_state_.state; \
Llatent_state_.togo += (w); \
} \
case __COUNTER__ - Llatent_base_: \
{ \
Llatent_state_.togo -= Llatent_delta_; \
Llatent_delta_ = 0; \
(var) = 1.0f - Max( Llatent_state_.togo / (w), 0.0f );
#define LATENT_FADE_OUT(w, var) \
++Llatent_state_.state; \
Llatent_state_.togo += (w); \
} \
case __COUNTER__ - Llatent_base_: \
{ \
Llatent_state_.togo -= Llatent_delta_; \
Llatent_delta_ = 0; \
(var) = Max( Llatent_state_.togo / (w), 0.0f );
|
EDIT: mimochodem kdo se to tu nedávno rozplýval nad "featurou" v nejmenovaném jazyce, který neumí propadávání přes casy u switche? |
|
Návrat nahoru |
|
|
Radis
Založen: 29. 03. 2014 Příspěvky: 235
|
Zaslal: 23. březen 2015, 11:41:51 Předmět: |
|
|
Haha. Implicitni switch fallthrough je castym zdrojem bugu, proto v modernich jazycich neni. V C# muzes v tech extremne raritnich pripadech, kdy je fallthrough vazne potreba, pouzit "goto case", to jen tak pro informaci.
Jinak misto stourani si radsi to svoje reseni uprav tak, abys mohl pouzivat lokalni promenne a cykly, jinak je to celkem na nic. Neco o tom, jak se v C++ daji implementovat generatory (semicoroutines), na googlu urcite najdes. |
|
Návrat nahoru |
|
|
quas4
Založen: 18. 10. 2007 Příspěvky: 199
|
Zaslal: 23. březen 2015, 11:47:58 Předmět: Re: "coroutines" v C++ (latent state) |
|
|
mar napsal: |
Možná se to tak běžně řeší, ale přijde mi to celkem fajn (hodlám skriptovat v C++). |
proc toto rozhodnuti? Do me dalsi planovane hry mam vymysleno embeddovat nejaky interpreter lispu ktery mi umozni podstrkavat skripty, funkce, menit kod a debugovat za behu. To vse se zakompilovanymi c++ "skripty" je myslim radove slozitejsi. Na druhou stranu asi neni potreba vymyslet datovou reflexi na miru. Jake vyhody prevazily pro rozhodnuti skriptovat v c++? |
|
Návrat nahoru |
|
|
mar
Založen: 16. 06. 2012 Příspěvky: 608
|
Zaslal: 23. březen 2015, 12:41:02 Předmět: Re: "coroutines" v C++ (latent state) |
|
|
Radis: pokud je toto u tebe častým zdrojem bugů, pak je něco špatně mezi židlí a klávesnicí. Jinak díky za radu, ale co je pro mě na nic, to s dovolením posoudím sám
quas4 napsal: |
proc toto rozhodnuti? Do me dalsi planovane hry mam vymysleno embeddovat nejaky interpreter lispu ktery mi umozni podstrkavat skripty, funkce, menit kod a debugovat za behu. To vse se zakompilovanymi c++ "skripty" je myslim radove slozitejsi. Na druhou stranu asi neni potreba vymyslet datovou reflexi na miru. Jake vyhody prevazily pro rozhodnuti skriptovat v c++? |
Mám pár důvodů, abych to shrnul:
- reflexi mám vyřešenou (traits+nějaká makra), nechci jít cestou metacompileru, nechci custom build step
- nechci řešit binding C++ <=> skript
- nechci integrovat 3rd party interpret ani psát vlastní
- ladění automaticky přes IDE
- neobávat se o výkon (i když neplánuji nic velkého, takže tohle by určitě problém nebyl)
- s posledním bodem souvisí i to, že iOS nepovoluje alokovat executable stránky, takže JIT nepřichází v úvahu
- moje lenost (asi nejdůležitější bod
Vidím ale samozřejmě i nevýhody oproti skriptu:
- skript může mít větší vyjadřovací schopnost (stavy apod.)
- elegance (engine může být zvlášť, logika je skriptovaná a nezávislá)
- možnost dynamického cachování skriptů (asi potenciálně zajímavé pouze pro obrovské projekty)
- doba překladu skriptu (pokud není předkompilovaný do bytecode) může být výrazně nižší, než u C++ kódu (EDIT: resp. doba linkování bude problém)
- jak jsi už napsal, nemožnost dynamických změn oproti skriptu |
|
Návrat nahoru |
|
|
mar
Založen: 16. 06. 2012 Příspěvky: 608
|
Zaslal: 23. březen 2015, 15:38:55 Předmět: Re: "coroutines" v C++ (latent state) |
|
|
Hmm, ještě mě napadla jedna trochu šílenost (vlastně už velmi dávno).
Jde to o simulovat různé stavy objektu pomocí dynamické změny vtbl pointeru. Prakticky to funguje dobře, teoreticky velmi nebezpečné
kód: |
#include <iostream>
using namespace std;
class Base
{
public:
virtual ~Base() {}
protected:
void SetVtbl( const void *vtbl ) {
*reinterpret_cast<const void **>(this) = vtbl;
}
};
#define DECL_VTBL_STUB(cls) \
private: static const void *myvtbl; \
public: static const void *GetVtbl() { \
return myvtbl; \
} \
static void StaticInit() { \
cls tmp; \
myvtbl = *reinterpret_cast<const void **>(&tmp); \
}
#define DEF_VTBL_STUB(cls) const void *cls::myvtbl = 0;
#define GOTO_STATE(cls) SetVtbl(cls::GetVtbl())
class Actor : public Base
{
public:
DECL_VTBL_STUB( Actor )
virtual void Die();
virtual void OnHit() {
cout << "Actor::OnHit" << endl;
}
};
class Actor_Dying : public Actor
{
public:
// pure state, must not add new member variables
DECL_VTBL_STUB( Actor_Dying )
void Die() {
cout << "forget it" << endl;
}
void OnHit() {
cout << "Actor_Dying::OnHit" << endl;
}
};
void Actor::Die() {
cout << "oh well" << endl;
GOTO_STATE( Actor_Dying );
}
DEF_VTBL_STUB(Actor)
DEF_VTBL_STUB(Actor_Dying)
void TestState()
{
Actor::StaticInit();
Actor_Dying::StaticInit();
Actor *a = new Actor;
a->OnHit();
a->Die();
a->OnHit();
a->Die();
delete a;
}
int main() {
TestState();
return 0;
}
|
|
|
Návrat nahoru |
|
|
Deluxe
Založen: 31. 07. 2007 Příspěvky: 235 Bydliště: Oslavany
|
Zaslal: 23. březen 2015, 19:20:55 Předmět: |
|
|
Co se tyka tech lokalnich promnennych, vzdycky se da udelat neco jako: http://www.fit.vutbr.cz/~peringer/SIMLIB/ (konkretne process.cc),
kde se to pouziva v implementaci "Next Event" algoritmu.
Ale portabilita jde pak do haje (a co to dela s vyjimkama je taky otazka). |
|
Návrat nahoru |
|
|
Radis
Založen: 29. 03. 2014 Příspěvky: 235
|
Zaslal: 24. březen 2015, 07:15:08 Předmět: |
|
|
mar: WTF? OOP a State Pattern jsou pro tebe moc mainstream?
Deluxe: Presne, taky jsem si pri cteni tohoto threadu vzpomnel na Peringera, jak na prednasce vykladal, ze se mu tam tuhle zalezitost podarilo "osklive nahackovat" Ale spis nez na SIMLIB bych se podival, jak se to resi treba v boostu. |
|
Návrat nahoru |
|
|
]semo[
Založen: 29. 07. 2007 Příspěvky: 1526 Bydliště: Telč
|
Zaslal: 24. březen 2015, 09:58:23 Předmět: |
|
|
Radis napsal: |
Haha. Implicitni switch fallthrough je castym zdrojem bugu, proto v modernich jazycich neni. |
Musím se Radise zastat, má pravdu. Dají se uvést i jiné příklady ze života. Třeba hoblík a dláto jsou častým zdrojem poranění a pokažení výrobku. Pro taky nebývají součástí moderních truhlářškých dílen. Laminovaná dřevotříska se dá zpracovávat bezpečněji například CNC pilkou. Komu se to nelíbí, je zpátečník. _________________ Kdo jede na tygru, nesmí sesednout.
---
http://www.inventurakrajiny.cz/sipka/
Aquadelic GT, Mafia II, simulátory |
|
Návrat nahoru |
|
|
mar
Založen: 16. 06. 2012 Příspěvky: 608
|
Zaslal: 24. březen 2015, 09:59:47 Předmět: |
|
|
Radis: co se ti nelíbí na dynamické změně vtbl?
To je přesně to, co se děje v konstruktorech/destruktorech virtuálních tříd.
Samozřejmě to řešení není dobré - ne proto, že by v praxi překladač umístil vtbl pointer jinam, než na první místo.
Pokud by se překladač z nějakého důvodu rozhodl devirtualizovat nějakou z těch tříd, tak je problém. Pokud by měl cachovaný vtbl ptr v registru, tak totéž.
A samozřejmě je problematické z toho pak odvozovat, pokud nechci v odvozené třídě reimplementovat daný stav.
Pokud jsi měl na mysli tohle: http://gameprogrammingpatterns.com/state.html, ok, nevidím v tom až takový rozdíl (i když je to samozřejmě safe), navíc musí předávat this ručně (což není nic dramatického).
Nicméně nechápu, proč tam ty stavy vytváří dynamicky, když si je může instancovat uvnitř.
Další věc je, jak pak bude takovýto stav ukladát (resp. jak to zaintegruje do reflexe).
Takže z mého pohledu opravdu ideální řešení nevidím, u jednoduché logiky bych klidně použil switch/ify a nekomplikoval to, nicméně na něco složitějšího to nebude úplně to pravé.
Co se týká toho SIMLIBu, tak oproti tahání stack pointeru je změna vtbl naprosto nevinná. |
|
Návrat nahoru |
|
|
mar
Založen: 16. 06. 2012 Příspěvky: 608
|
Zaslal: 24. březen 2015, 10:04:51 Předmět: |
|
|
]semo[ napsal: |
Musím se Radise zastat, má pravdu. Dají se uvést i jiné příklady ze života. Třeba hoblík a dláto jsou častým zdrojem poranění a pokažení výrobku. Pro taky nebývají součástí moderních truhlářškých dílen. Laminovaná dřevotříska se dá zpracovávat bezpečněji například CNC pilkou. Komu se to nelíbí, je zpátečník. |
Díky (pokud to nebylo myšleno ironicky, občas se ztrácím, kdy kdo co myslí vážně a kdy ne
Výjimečně s tebou nesouhlasím. Pokud se bavíme o C++, je tam tolik možností, jak se poranit, že si to mám vyložit tak, že doporučuješ přejít na jiný, bezpečnější programovací jazyk? |
|
Návrat nahoru |
|
|
rezna
Založen: 27. 07. 2007 Příspěvky: 2156
|
Zaslal: 24. březen 2015, 10:14:37 Předmět: |
|
|
IMHO to bylo mysleno tezce ironicky |
|
Návrat nahoru |
|
|
|