.[ ČeskéHry.cz ].
"coroutines" v C++ (latent state)
Jdi na stránku 1, 2  Další
 
odeslat nové téma   Odpovědět na téma    Obsah fóra České-Hry.cz -> Obecné
Zobrazit předchozí téma :: Zobrazit následující téma  
Autor Zpráva
mar



Založen: 16. 06. 2012
Příspěvky: 602

PříspěvekZaslal: 20. březen 2015, 04:40:47    Předmět: "coroutines" v C++ (latent state) Odpovědět s citátem

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
Zobrazit informace o autorovi Odeslat soukromou zprávu
perry



Založen: 28. 07. 2009
Příspěvky: 879

PříspěvekZaslal: 20. březen 2015, 09:11:47    Předmět: Odpovědět s citátem

Možná se zeptám hodně blbě, ale jak funguje např. to wait? Nebo podle čeho to vlastně čeká...
_________________
Perry.cz
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu Zobrazit autorovi WWW stránky
mar



Založen: 16. 06. 2012
Příspěvky: 602

PříspěvekZaslal: 20. březen 2015, 09:23:48    Předmět: Odpovědět s citátem

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
Zobrazit informace o autorovi Odeslat soukromou zprávu
perry



Založen: 28. 07. 2009
Příspěvky: 879

PříspěvekZaslal: 20. březen 2015, 10:39:38    Předmět: Odpovědět s citátem

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 Wink
_________________
Perry.cz
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu Zobrazit autorovi WWW stránky
mar



Založen: 16. 06. 2012
Příspěvky: 602

PříspěvekZaslal: 23. březen 2015, 10:40:44    Předmět: Re: "coroutines" v C++ (latent state) Odpovědět s citátem

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? Smile
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
Radis



Založen: 29. 03. 2014
Příspěvky: 235

PříspěvekZaslal: 23. březen 2015, 11:41:51    Předmět: Odpovědět s citátem

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
Zobrazit informace o autorovi Odeslat soukromou zprávu
quas4



Založen: 18. 10. 2007
Příspěvky: 199

PříspěvekZaslal: 23. březen 2015, 11:47:58    Předmět: Re: "coroutines" v C++ (latent state) Odpovědět s citátem

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
Zobrazit informace o autorovi Odeslat soukromou zprávu
mar



Založen: 16. 06. 2012
Příspěvky: 602

PříspěvekZaslal: 23. březen 2015, 12:41:02    Předmět: Re: "coroutines" v C++ (latent state) Odpovědět s citátem

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 Smile

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 Smile

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
Zobrazit informace o autorovi Odeslat soukromou zprávu
mar



Založen: 16. 06. 2012
Příspěvky: 602

PříspěvekZaslal: 23. březen 2015, 15:38:55    Předmět: Re: "coroutines" v C++ (latent state) Odpovědět s citátem

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é Smile

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
Zobrazit informace o autorovi Odeslat soukromou zprávu
Deluxe



Založen: 31. 07. 2007
Příspěvky: 235
Bydliště: Oslavany

PříspěvekZaslal: 23. březen 2015, 19:20:55    Předmět: Odpovědět s citátem

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
Zobrazit informace o autorovi Odeslat soukromou zprávu
Radis



Založen: 29. 03. 2014
Příspěvky: 235

PříspěvekZaslal: 24. březen 2015, 07:15:08    Předmět: Odpovědět s citátem

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" Smile Ale spis nez na SIMLIB bych se podival, jak se to resi treba v boostu.
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
]semo[



Založen: 29. 07. 2007
Příspěvky: 1525
Bydliště: Telč

PříspěvekZaslal: 24. březen 2015, 09:58:23    Předmět: Odpovědět s citátem

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
Zobrazit informace o autorovi Odeslat soukromou zprávu
mar



Založen: 16. 06. 2012
Příspěvky: 602

PříspěvekZaslal: 24. březen 2015, 09:59:47    Předmět: Odpovědět s citátem

Radis: co se ti nelíbí na dynamické změně vtbl? Smile
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
Zobrazit informace o autorovi Odeslat soukromou zprávu
mar



Založen: 16. 06. 2012
Příspěvky: 602

PříspěvekZaslal: 24. březen 2015, 10:04:51    Předmět: Odpovědět s citátem

]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 Smile
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? Wink
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
rezna



Založen: 27. 07. 2007
Příspěvky: 2156

PříspěvekZaslal: 24. březen 2015, 10:14:37    Předmět: Odpovědět s citátem

IMHO to bylo mysleno tezce ironicky Wink
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
Zobrazit příspěvky z předchozích:   
odeslat nové téma   Odpovědět na téma    Obsah fóra České-Hry.cz -> Obecné Časy uváděny v GMT + 1 hodina
Jdi na stránku 1, 2  Další
Strana 1 z 2

 
Přejdi na:  
Nemůžete odesílat nové téma do tohoto fóra
Nemůžete odpovídat na témata v tomto fóru
Nemůžete upravovat své příspěvky v tomto fóru
Nemůžete mazat své příspěvky v tomto fóru
Nemůžete hlasovat v tomto fóru


Powered by phpBB © 2001, 2005 phpBB Group


Vzhled udelal powermac
Styl "vykraden" z phpBB stylu MonkiDream - upraveno by rezna