Autor Wątek: Nieblokująca Lua  (Przeczytany 7715 razy)

Offline Xirdus

  • Redaktor

# Czerwiec 20, 2014, 18:35:21
Piszę sobie grę, której zasadnicza część będzie się odbywać w skryptach Lua. Mam jednak problem z zaprojektowaniem komunikacji silnik<->skrypty. Problem generalnie sprowadza się do tego, jak zrobić, żeby na komendę "krok wprzód", skrypt poczekał aż w grze ten ruch się wykona i dopiero wtedy szedł dalej. Jak na razie widzę cztery możliwe rozwiązania:
  • Zabawa z lua_yield i lua_resume - z tym że wszędzie w internetach widzę przykłady z użyciem coroutines, a ja będę miał tylko jedną, główną coroutine - czy zatrzymanie wszystkich jednej może powodować jakieś problemy? No i diametralnie skomplikuje to kod wywołujący skrypt, oraz kod wywołujący kod wywołujący skrypt (bo podczas yielda musi działać pętla główna gry).
  • Skrypty wykonywane w osobnym wątku, oddzielnym od pętli głównej gry - można wtedy zastosować semafor czy coś, ale dochodzą oczywiste problemy z synchronizacją.
  • Wykonywanie skryptów linijka po linijce - da się tak w ogóle?
  • Olanie Lua i zrobienie języka skryptowego i interpretera szytego na miarę - rozwiązanie ostateczne.

Miał ktoś już podobny problem? Jeśli tak to jak do niego podszedł? Które z powyższych wydaje wam się najlepszym rozwiązaniem?

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline yarpen

  • Użytkownik

# Czerwiec 20, 2014, 19:09:28
yield/resume. Do tego jest stworzone.

Offline ΨΧΞ

  • Użytkownik
    • PsichiX Website

  • +4
# Czerwiec 20, 2014, 21:46:18
4. nie chcesz, za prawde Ci powiem, nie chcesz za nic tego robic.

Offline Xirdus

  • Redaktor

# Czerwiec 20, 2014, 22:26:27
yield/resume. Do tego jest stworzone.
Wiesz może gdzie znaleźć jakieś zgrabne przykłady użycia tegoż? W Googlu mogę jedynie znaleźć luowe yield() używane przy coroutines(), a dokumentacja za wiele nie wyjaśnia. No i czy są może jakieś C++-owe wrappery które to zgrabnie opakowują, czy będę musiał pisać własny?

4. nie chcesz, za prawde Ci powiem, nie chcesz za nic tego robic.
Wiem. Przeto byłem napisałem, że to rozwiązanie ostateczne.

Offline MatlertheGreat

  • Użytkownik

# Czerwiec 20, 2014, 22:45:09
Przeto byłem napisałem ...
Pierwszy raz widzę użycie czasu zaprzeszłego(?) w internetach w ojczystym języku. Taka konstrukcja jest w polskim dozwolona, czy to taki słowny żart?

#OMGbanzaofftop

Online lethern

  • Użytkownik

# Czerwiec 20, 2014, 23:03:43
mi się podoba "kod wywołujący kod wywołujący" :D
lol, musiałem

Offline Xion

  • Moderator
    • xion.log

# Czerwiec 20, 2014, 23:25:15
Cytuj
Taka konstrukcja jest w polskim dozwolona, czy to taki słowny żart?
Jest formalno-literacko-archaiczna, ale AFAIK wciąż poprawna. Oczywiście w tym kontekście Xirdus sobie wyłącznie manieryzuje (vide "przeto"), bo nie ma żadnej niejednoznaczności co do następstwa zdarzeń w jego wypowiedzi.

Wracając do tematu:
Cytuj
Problem generalnie sprowadza się do tego, jak zrobić, żeby na komendę "krok wprzód", skrypt poczekał aż w grze ten ruch się wykona i dopiero wtedy szedł dalej.
Dlaczego skrypt miałby "czekać"? Lua nie ma żadnych callbacków/delegatów/eventów/listenerów/kanałów/whatever-else, których można by użyć do napisania handlera onMoveForward, który byłby potem wywoływany przez kod gry?

Offline Rolek

  • Użytkownik

# Czerwiec 20, 2014, 23:41:04
<offtop>
byłem napisałem
Poprawnie powinno być "był napisałem" lub "napisałem był" ;)
</offtop>

Offline yarpen

  • Użytkownik

# Czerwiec 20, 2014, 23:43:12
Wiesz może gdzie znaleźć jakieś zgrabne przykłady użycia tegoż? W Googlu mogę jedynie znaleźć luowe yield() używane przy coroutines(), a dokumentacja za wiele nie wyjaśnia. No i czy są może jakieś C++-owe wrappery które to zgrabnie opakowują, czy będę musiał pisać własny?
Nie ma tu za wiele do wrappowania, kilka linijek raptem. Jakas struktura z czasem/warunkiem pobudki + lua_State, wolasz lua_yield i kiedy trzeba skrypt obudzic wolasz lua_resume. Koniec.

Offline Xirdus

  • Redaktor

# Czerwiec 21, 2014, 00:16:14
mi się podoba "kod wywołujący kod wywołujący" :D
lol, musiałem
Enkapsulacja! :P

<offtop>Poprawnie powinno być "był napisałem" lub "napisałem był" ;)
</offtop>
Dzięki, zapamiętam :)

Wracając do tematu:Dlaczego skrypt miałby "czekać"? Lua nie ma żadnych callbacków/delegatów/eventów/listenerów/kanałów/whatever-else, których można by użyć do napisania handlera onMoveForward, który byłby potem wywoływany przez kod gry?
Piszę oldschoolowe RPG. Skrypty w założeniu mają być proste: dodaj przedmiot, porusz postacią, wyświetl dialog, rozpocznij walkę, pokaż cutscenkę. Każdą z tych akcji da się wyrazić jedną linijką skryptu, i prawie każda powoduje kilkusekundowy przestój w skrypcie do czasu reakcji gracza, i w tym czasie animacja ma cały czas działać. Gdybym robił to callbackami/whatever, zamiast dziesięciolinijkowego skryptu miałbym dziesięciofunkcyjny, z niezłym boilerplate'em. Lepiej już by było w takim wypadku wykonywać skrypty po jednej linijce naraz, ale spowolniłoby to skrypty nietrywialne, i nie wiem czy by nie rozwaliło struktury kodu skryptu - ifów, pętli, wywołań funkcji. Tak więc yieldy wydają się najsensowniejsze.

Nie ma tu za wiele do wrappowania, kilka linijek raptem. Jakas struktura z czasem/warunkiem pobudki + lua_State, wolasz lua_yield i kiedy trzeba skrypt obudzic wolasz lua_resume. Koniec.
No tak, na gołej Lua to by było OK. Problem pojawia się z C++-owymi wrapperami typu Luabind, których chcę użyć, a które yieldów nie wspierają (gdy skrypt wywołany przez pcall() yielduje, Lua traktuje to jako błąd wykonania - trzeba używac pcallk(), którego większość wrapperów nie obsługuje (sprawdzałem grepem na źródłach) - jednak to nie jest już problem, ponieważ znalazłem taki wrapper, który to wspiera (mam nadzieję): lua-intf).

Offline Xion

  • Moderator
    • xion.log

  • +1
# Czerwiec 21, 2014, 02:22:03
Cytuj
Każdą z tych akcji da się wyrazić jedną linijką skryptu, i prawie każda powoduje kilkusekundowy przestój w skrypcie do czasu reakcji gracza, i w tym czasie animacja ma cały czas działać. Gdybym robił to callbackami/whatever, zamiast dziesięciolinijkowego skryptu miałbym dziesięciofunkcyjny, z niezłym boilerplate'em.
Irrelevant. Pytanie brzmi czy uruchamiasz skrypty tylko w odpowiedzi na konkretne zdarzenia w mechanice gry, czy to skrypt opisuje całą mechanikę i jedynie woła kod gry. W pierwszym przypadku bardziej pasują callbacki/eventy/etc., w drugim wystawienie odpowiedniego API i być może coroutines.

Offline Xirdus

  • Redaktor

# Czerwiec 21, 2014, 02:49:58
Irrelevant. Pytanie brzmi czy uruchamiasz skrypty tylko w odpowiedzi na konkretne zdarzenia w mechanice gry, czy to skrypt opisuje całą mechanikę i jedynie woła kod gry. W pierwszym przypadku bardziej pasują callbacki/eventy/etc., w drugim wystawienie odpowiedniego API i być może coroutines.
Technicznie to skrypty odpowiadają na callbacki. Jednak IMO to właśnie to nie ma znaczenia - bo problemem jest nie jest wywołanie skryptu z kodu gry czy kodu gry ze skryptu, tylko kontynuowanie pętli głównej gry (z 5 ramek stosu niżej niż kod startujący skrypt) bez przerwania działania skryptu.

Offline laggyluk

  • Użytkownik
    • http://laggyluk.com

# Czerwiec 21, 2014, 13:29:28
Skrypty w założeniu mają być proste: dodaj przedmiot, porusz postacią, wyświetl dialog, rozpocznij walkę, pokaż cutscenkę. Każdą z tych akcji da się wyrazić jedną linijką skryptu, i prawie każda powoduje kilkusekundowy przestój w skrypcie do czasu reakcji gracza
pomysł: jeżeli masz callback na koniec każdej akcji to możesz zrobić tak że lista nie jest skryptem lua ale na jej podstawie generujesz właściwy kod z użyciem callbacków albo tych yieldów

Offline Xirdus

  • Redaktor

# Czerwiec 21, 2014, 14:53:19
pomysł: jeżeli masz callback na koniec każdej akcji to możesz zrobić tak że lista nie jest skryptem lua ale na jej podstawie generujesz właściwy kod z użyciem callbacków albo tych yieldów

Yieldy wydają się tu sensowniejsze, szybsze, nie rozwalają ifów/pętli/funkcji, i nie wymagają generacji kodu.

Chociaż dalej na necie nie mogę znaleźć żadnych normalnych przykładów, za to wpadłem na jakiegoś dziwnego posta na SO że "główny wątek" Lua jest specjalny i "nie może" yieldować... Cóż, trza odpalić IDE i się pobawić.

Offline Xion

  • Moderator
    • xion.log

# Czerwiec 21, 2014, 15:10:06
Cytuj
Jednak IMO to właśnie to nie ma znaczenia - bo problemem jest nie jest wywołanie skryptu z kodu gry czy kodu gry ze skryptu, tylko kontynuowanie pętli głównej gry (z 5 ramek stosu niżej niż kod startujący skrypt) bez przerwania działania skryptu.
Zapędziłeś się w jakieś dziwne mentalne pudełko. Dlaczego skrypt ma startować raz, potem nie przerywać swojego działania podczas gdy pętla gry wciąż się kręci? Jak dla mnie nie ma sensu, chyba że kawałek logiki w skrypcie odpowiada za jakiś długo działający proces; z tego co piszesz to jednak tak nie jest.

Opisując to jeszcze inaczej: twoja "pętla główna gry" to nic innego jak event loop. Wysyłasz z niej np. eventy update'u logiki i renderowania -- oczywiście z wierzchu to są zwykłe wywołania funkcji z kodu gry, ale nic nie stoi na przeszkodzie, żeby były to funkcje napisane w zewnętrznym skrypcie.

Oczywiście mogę się mylić, bo tylko domyślam się, co chcesz osiągnąć. Nie podałeś bowiem żadnego -- choćby wymyślnego -- przykładu skryptu, w takiej formie, jaką masz na myśli.