Sprawa akcji nie jest wcale taka trudna, jak mogło by się wydawać...
Wystarczy jedna tabela z kolejką akcji, w której zapisujemy:
1. Akcja do wykonania - tutaj jest problem z identyfikacją różnych akcji - zwykły licznik to trochę za mało przy dużej ilości akcji.
2. Identyfikator użytkownika, który wykonuje akcję.
3. Moment, w którym akcja powinna być wykonana.
Kiedy potrzebujemy danych użytkownika (w celu odbycia walki, szpiegowania etc.) wybieramy akcje wszystkich użytkowników, których informacje muszą być uaktualnione. Przetwarzamy je po kolei zgodnie z czasem wykonania.
Wcale nie trzeba w kółko przetwarzać całej kolejki. Pozostaje kwestia odśmiecania kolejki. Gracz, który przez miesiąc nie będzie grał nazbiera 1000 akcji, a kiedy będziemy chcieli podglądnąć stan jego konta trzeba obliczyć wszystko po kolei. Dobrym rozwiązaniem byłby jakiś prosty skrypt przetwarzający akcje uruchamiany przez crona na przykład co godzinę.
Drugie podejście...
Mamy już serwer gry (php w tym przypadku odpada). Nie musimy nic zapisywać w bazie, wystarczy na serwerze utworzyć kolejkę z akcjami. Obiekty akcji użytkownika nazwiemy UserAction. Interfejs wygląda mniej więcej tak:
Kod
int getTime(); // Zwraca znacznik czasu wskazujący na moment wykonania akcji.
void call(); // Wywołuje akcję
W momencie, w którym użytkownik żąda wywołania akcji obliczamy czas, w którym ma być wykonana i tworzymy obiekt akcji. Dodajemy akcję na koniec kolejki. Do przetwarzania potrzebujemy:
- Timera (czas aktualizacji to dokładność znacznika czasu, najczęściej 1s)
- Licznika, w którym zapiszemy ile sekund pozostało do wykonania pierwszej akcji w kolejce. Tak będzie szybciej niż pobierając za każdym razem czas z obiektu.
Zapisując to krokowo:
-- Ładowanie akcji --
1. Pobieramy znacznik czasu z pierwszego obiektu w kolejce.
2. Obliczamy różnicę: czas akcji - czas obecny. Zapisujemy wynik w liczniku.
-- Pętla licznika --
3. Sprawdzamy czy licznik nie jest niedodatni (może się tak zdarzyć, kiedy poprzednia akcja będzie wykonywana dłużej lub tyle samo co przerwa pomiędzy nią a następną). Jeżeli jest, wtedy przechodzimy do punktu 5.
4. Odejmujemy 1 od licznika, przechodzimy do punktu 3.
-- Wykonywanie akcji --
5. Wywołujemy akcję.
6. Gdy wykonanie akcji jest zakończone sukcesem, wtedy usuwamy pierwszy element kolejki (wykonaną akcję) i przechodzimy do punktu 1.
Wydaje mi się, że jest to w miarę wydajny sposób na wykonanie sekwencji akcji w czasie rzeczywistym. Timer nie pochłania zbyt dużej mocy obliczeniowej procesora. Jałowe przebiegi pętli licznika to tylko jedna instrukcja odejmowania i jedno porównanie, wykonywane w dosyć dużych odstępach czasu w porównaniu do złożoności obliczeń. Kolejka jest w tym wypadku bardzo wydajną strukturą, gdyż interesują nas tylko odwołania do pierwszego i ostatniego obiektu. Kolejne obiekty wiążemy w łańcuch przy pomocy referencji. Nie ma problemu z alokacją pamięci.
Wybór techniki zależy od wielu czynników... Mam nadzieję, że napisałem to jasno...