Jump to content
  • Chmurka
  • Boróweczka
  • Jabłuszko
  • Limonka
  • Czekoladka
  • Węgielek

Recommended Posts

Cześć! W trakcie próby optymalizacji naszego kodu, skracamy go, staramy się lepiej zarządzać pamięcią, ucinamy maksymalną liczbę graczy na serwerze w pętlach przebiegających po liście klientów czy pobieramy cvary tylko na początku mapy a nie "na żywo". Ale bardzo często zapominamy o tym, że możemy w prosty sposób zoptymalizować nasze warunki IF. Jak?

 

Różne języki programowania mają różne sposoby na sprawdzanie końcowej wartości boolean w warunkach. W Sourcemod'zie występuje rodzaj sprawdzania o nazwie "Minimalna Ewaluacja". W prostych słowach oznacza to, że jeśli mamy warunek IF w którym znajduje się więcej niż jedno wyrażenie do porównania, to program będzie sprawdzał je od lewej do prawej. Ale to nie koniec. Najlepiej jeśli wyjaśnię to na przykładach:

if (warunek1 && warunek2) { /* ...kod... */ }

W tym warunku możemy mieć 4 przypadki:

  • warunek1 == true  oraz warunek2 == true    (IF uznaje że całościowo jego wartość to true)
  • warunek1 == true  oraz warunek2 == false   (IF uznaje że całościowo jego wartość to false)
  • warunek1 == false oraz warunek2 == true    (IF uznaje że całościowo jego wartość to false)
  • warunek1 == false oraz warunek2 == false   (IF uznaje że całościowo jego wartość to false)

W przypadku gdy warunek1 to true, program musi sprawdzić jeszcze warunek2 by móc określić jaką w sumie ma wartość. Więc standardowo wykonają się dwa sprawdzenia i nie ma tutaj żadnej magii. Co innego dzieje się gdy pierwszy z warunków wynosi false. Jak wiemy (albo możemy się dowiedzieć czytając poradnik MAGNET'a o operacjach bitowych
Hej! Skorzystałeś z linku lub pobrałeś załącznik? Uhonoruj naszą pracę poprzez rejestrację na forum i rośnij razem z nami!) operacja && (inaczej AND, "oraz" lub koniunkcja) jest true tylko i wyłącznie jeśli oba warunki są true. Więc jeśli warunek1 jest fałszem, Minimalna Ewaluacja w Sourcemod'zie sprawia, że drugiego warunku już nie sprawdzamy! Ponieważ nie ma znaczenia jaką wartość ma warunek2, jeśli warunek1 jest false to cały IF będzie przyjmował wartośc false.

Inny przykład:

if (warunek1 || warunek2) { /* ...kod... */ }

Tutaj posługujemy się || (OR, "lub" albo alternatywa). Również możemy mieć 4 przypadki:

  • warunek1 == true  oraz warunek2 == true    (IF uznaje że całościowo jego wartość to true)
  • warunek1 == true  oraz warunek2 == false   (IF uznaje że całościowo jego wartość to true)
  • warunek1 == false oraz warunek2 == true    (IF uznaje że całościowo jego wartość to true)
  • warunek1 == false oraz warunek2 == false   (IF uznaje że całościowo jego wartość to false)
Sytuacja jest prawie identyczna. Tyle, że jeśli warunek1 wynosi false, program będzie musiał sprawdzić drugą wartość. A jeśli warunek1 wynosi true, to program nie musi sprawdzać warunek2, bo niezależnie od wartości w warunek2, jeśli warunek1 == true, to cały IF zawsze będzie true.



Przykład z życia
Najbardziej popularną funkcją która jest jednym wielkim warunkiem jest IsValidClient, szeroko używana prawie w każdym pluginie, nawet jeśli nie w zwartej funkcyjnej formie, to jako sam warunek. Jej zadanie to sprawdzenie, czy klient jest "poprawny" czyli czy jest na serwerze, gra i jest człowiekiem (nie jest botem). Wygląda ona tak:
stock bool IsValidClient(int client)
{//Rozłożyłem warunek na parę linii żeby można było chwilowo dobrze go odczytać, nie róbcie tak z własnymi IFami ;)
	return (
      1 <= client <= MaxClients && 
      IsClientConnected(client) && 
      IsClientInGame(client) && 
      !IsFakeClient(client) && 
      !IsClientSourceTV(client)
    );
}

Pierwszy warunek:

1 <= client <= MaxClients

Jest najważniejszy i "najmocniejszy". Dlaczego? Ponieważ wyrzuca nam największą ilość nieprawidłowych client'ów. Dlaczego? Byty (entity) na których możemy wywołać tę funkcję (w tym gracze) wahają swoje ID od 0 do teoretycznej nieskończoności (chociaż tak, jest limit bytów na mapie). Więc od razu, prostym porównaniem dwóch liczb (zauważmy, że nie pobieramy tutaj niczego) dla bardzo dużej ilości ID zamiast sprawdzać wszystkie 5 warunków, sprawdzamy tylko 1. Tylko maksymalnie 64 ID mogą przejść do sprawdzenia następnego warunku:

IsClientConnected(client) && 
IsClientInGame(client) && 

Te dwa warunki omówimy razem ponieważ są bardzo podobne. Connected sprawdza, czy gracz łączy się lub jest już połączony z serwerem. InGame sprawdza, czy jest już w grze. W teorii, InGame może wyeliminować nam więcej nieprawidłowych ID (ponieważ jeśli gracz już jest w grze to na pewno jest połączony, więc nie musimy sprawdzać już czy jest połączony) ale niestety jeśli gracz nie jest połączony z serwerem, to InGame wywala nam error w konsoli. Dzięki tym warunkom, eliminujemy załóżmy połowę nieprawidłowych ID jeśli serwer jest tylko w połowie zapełniony. Następne warunki eliminują jeszcze mniej przypadków:

!IsFakeClient(client) && 
!IsClientSourceTV(client)

IsFakeClient sprawdza, czy klient nie jest botem. Może nam to wyeliminować parę nieprawidłowych ID, chociaż 90% serwerów nie używa botów. No i ostatni warunek sprawdzający czy ID jest GOTV, może nam wyeliminować maksymalnie jeden przypadek. Dlatego znajduje się na końcu.

 

 

 

Przykład z życia 2

W niektórych przypadkach, możemy jeszcze bardziej zoptymalizować nawet tak dobrze zrobiony warunek jak IsValidClient. Na myśl przychodzi mi konkretnie Nowy Cod by Linux
Hej! Skorzystałeś z linku lub pobrałeś załącznik? Uhonoruj naszą pracę poprzez rejestrację na forum i rośnij razem z nami! oraz jego klasy. W praktycznie każdej klasie codmod znajduje się zmienna "ma_klase[client]", która określa czy dany klient ma daną klasę i czy może korzystać z jej umiejętności. Oczywiście będziemy również tam używali IsValidClient. Ale inaczej! Mamy kod:

public Action OnTakeDamage(client, int &attacker, int &inflictor, float &damage, int &damagetype)
{
	if(!IsValidClient(attacker) || !IsValidClient(client) || GetClientTeam(client) == GetClientTeam(attacker))
		return Plugin_Continue;
	
	if (ma_klase[attacker])
	{
		damage *= 1.5;
		return Plugin_Changed;
	}
	
	return Plugin_Continue;
}

Ten kod, który zaraz będziemy optymalizować na pierwszy rzut oka nie ma żadnych wad. Standardowo sprawdzamy czy klienci związani z akcją są na serwerze oraz czy nie są w tej samej drużynie, potem sprawdzamy czy atakujący posiada odpowiednią klasę, jeśli tak, to powiększamy zadane przez niego obrażenia o 50%. Przedstawiam bardziej zoptymalizowaną wersję:

public Action OnTakeDamage(client, int &attacker, int &inflictor, float &damage, int &damagetype)
{
	if(1 <= attacker <= MaxClients && ma_klase[attacker] && !IsFakeClient(attacker) && !IsClientSourceTV(attacker))
	{
		if(!IsValidClient(client) || GetClientTeam(client) == GetClientTeam(attacker))
			return Plugin_Continue;
		
		damage *= 1.5;
		return Plugin_Changed;
	}
	return Plugin_Continue;
}

 

1. Zamiast korzystać ze standardowego IsValidClient, modyfikujemy tę funkcje tak, by zaraz po sprawdzeniu czy ID klienta mieści się w przedziale 1-64 sprawdzało się czy gracz ma klasę. Dlaczego? W trybie cod, jest dużo klas, minimalnie 7 jeśli nie 30. W najgorszym przypadku (7 klas) jeśli wybór klas przez graczy jest równomierny, przy pełnym serwerze 64 slotowym eliminujemy aż 64/7 ~ = 90% przypadków! Ten procent zwiększa się im klasa jest mniej popularna/im więcej klas jest na serwerze (im mniej graczy gra daną klasą w danym momencie tym więcej przypadków ten IF wyeliminuje).

 

2. Ponadto ten warunek jest "naturalny", nie pobiera żadnych danych tylko działa na zapisanych już w pluginie wartościach.
 

3. Rezygnujemy kompletnie z IsClientInGame oraz IsClientConnected. Dlaczego? Ponieważ jeśli jakikolwiek ID gracza atakującego (1 <= attacker <= MaxClients) oraz posiadającego klasę zadało jakiekolwiek obrażenia (jesteśmy przypominam w OnTakeDamage, funkcji która wywołuje się tylko jeśli ktoś otrzyma obrażenia) to musi być ten gracz podłączony do serwera! Podobnie moglibyśmy zrobić z IsValidClient sprawdzającego ID client'a. Jeśli ID otrzymuje obrażenia i ma ID klienta (w przedziale 1-64) to na pewno jest żywy i połączony z serwerem


 

 

Bibliografia

https://forums.alliedmods.net/showthread.php?t=313306

https://forums.alliedmods.net/showthread.php?t=188033

https://en.wikipedia.org/wiki/Short-circuit_evaluation

  • Lubię to! 3

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

Nasza historia

Na początku byliśmy małą grupą internetowych znajomych, którzy stwierdzili, że potrzebne jest solidne forum, na którym znajdą się ludzie z dużą wiedzą programistyczną ukierunkowaną na CS:GO. Pomysł powstał na początku 2018 roku, a parę miesięcy później, 19 kwietnia, powstała ta strona internetowa. Jako alternatywna odpowiedź na inne tego typu miejsca, poważnie podeszliśmy do tematu, najpierw tłumacząc angielską dokumentację SourceMod'a na język polski, a potem pisząc rozległe poradniki i wypełniając forum najpotrzebniejszymi rzeczami dla właścicieli serwerów i programistów. Cała nasza Ekipa jest dumna z pracy jaką w to włożyliśmy i cieszymy się że zbierają się wokół nas zarówno ludzie znający tematy sourcepawn'a i konfiguracji, jak i również nowe twarze w tym "biznesie", którym z chęcią niesiemy wiedzę oraz pomoc w rozwiązywaniu problemów.

Największe modyfikacje serwerowe

×
×
  • Create New...