Opne.mp'de test etme
SA:MP'de test yapmak pek kolay değildir. Çoğunlukla sadece sunucuya bağlanmak ve her yere yazdırma işlemi yer alır. y_testing gibi kütüphaneler, birim testlerini yazmayı daha kolay hale getirir, ancak genellikle "saf" kodlar için geçerlidir - yani, oyuncu etkileşimi olmadan sunucuda çalışan kodlar için. Bu şekilde birçok sistemi test edebilirsiniz, ancak hala sınırlıdır ve sorunlar ortaya çıktığında bu sorunları daraltmada yardımcı olmaz.
Peki open.mp bununla nasıl başa çıkıyor? İlk olarak, kod açık kaynaklı olduğundan, GDB ve Visual Studio gibi mevcut hata ayıklama araçlarına tam erişiminiz vardır. Ayrıca, pawn kodlamaları için benzer araçları eklemeyi planlıyoruz.
Ancak en büyük ek, "mock" kütüphanesidir. Çok basitçe, bu size gerçek oyuncular gibi davranan, ancak bağlı bir istemci olmadan çalışan sahte oyuncular oluşturmanıza olanak tanır. Bu sayede, bir kod tarafından gerçekleştirilen herhangi bir eylem, başka bir kod tarafından okunabilir ve analiz edilebilir. İlk bir gösterim için en basit örnekten birini ele alalım - bir oyuncuya bir kontrol noktasını göstermek ve bunun gönderildiğini doğrulamak. y_testing ile eylemi gerçekleştirirsiniz ve ardından gerçekten oyuncuya bunun gerçekleşip gerçekleşmediğini sormak zorundasınız, bu da yavaş, tekrarlanamaz ve sadece sıkıcıdır:
PTEST__ SetPlayerCheckpoint(playerid)
{
SetPlayerCheckpoint(playerid, 0.0, 0.0, 4.0, 5.0);
ASK("Dünyada bir kontrol noktası görüyor musunuz?");
}
"Pawn.RakNet" gibi bir kütüphane kullanmak, çıkış paketlerini otomatik olarak kontrol etmek suretiyle soruyu ortadan kaldırabilir, ancak hala bağlı bir oyuncu gerektirir, bu da otomatikleştirilemez ve tekrarlanamaz:
static
gCheckpointPlayer,
Float:gX,
Float:gY,
Float:gZ,
Float:gS;
PTEST__ SetPlayerCheckpoint(playerid)
{
SetPlayerCheckpoint(playerid, 0.0, 0.0, 4.0, 5.0);
ASSERT(gX == 0.0);
ASSERT(gY == 0.0);
ASSERT(gZ == 4.0);
ASSERT(gS == 5.0);
ASSERT(gCheckpointPlayer == playerid);
}
OPacket:107(playerid, BitStream:bs)
{
gCheckpointPlayer = playerid;
BS_IgnoreBits(bs, 8);
BS_ReadFloat(bs, gX);
BS_ReadFloat(bs, gY);
BS_ReadFloat(bs, gZ);
BS_ReadFloat(bs, gS);
return 1;
}
"open.mp" ile, Pawn.RakNet'e benzer bir API'ya sahibiz, ancak sahte oyuncularla. Yapay bir oyun örneği olmadan bir oyuncu oluşturursunuz ve bunu tam olarak normal bir oyuncu gibi kullanırsınız:
TEST__ SetPlayerCheckpoint()
{
new playerid = Mock_CreatePlayer("Mr Mock");
// Mevcut tüm paketleri temizle, arama işlemini kolaylaştır.
Mock_PurgeSent(playerid);
// Normal olarak bir kontrol noktası göster.
SetPlayerCheckpoint(playerid, 0.0, 0.0, 4.0, 5.0);
// Bir 'SetCheckpoint' paketinin görüldüğünü kontrol et.
new MockPacket:packet = Mock_GetPacket(playerid, "SetCheckpoint");
ASSERT(packet);
if (!packet) return;
// Paketin 4 bileşen içerdiğini ve her birinin 32 bit olduğunu kontrol et.
ASSERT(MockPacket_Components(packet) == 4);
ASSERT(MockPacket_Bits(packet) == 4 * 32);
// Farklı bileşenleri kontrol et.
new Float:tmp;
ASSERT(MockPacket_ReadFloat(packet, 0, tmp));
ASSERT(tmp == 0.0);
ASSERT(MockPacket_ReadFloat(packet, 1, tmp));
ASSERT(tmp == 0.0);
ASSERT(MockPacket_ReadFloat(packet, 2, tmp));
ASSERT(tmp == 4.0);
ASSERT(MockPacket_ReadFloat(packet, 3, tmp));
ASSERT(tmp == 5.0);
}
Bu kod tamamen tekrarlanabilir bir şekilde test edilebilir, kendi içinde kapalı (global değişkenler ve ekstra geri çağrılar olmadan) ve genişletmeye uygun bir şekilde basittir.
Ancak bazen bu kadar basit olmayabilir. Bir nesne oluşturduğunuzda, oyuncu hemen kontrol edilemez, çünkü belki de ona henüz gönderilmemiş olabilirler ve bu nedenle yerleşik akışı gönderici henüz onlara göndermemiştir. Bu durumu çözmek için, önce SetPlayerPos
gibi şeyler için temel senkronizasyonun nasıl çalıştığını açıklamak için biraz sapmalıyız. Bir oyuncunun pozisyonunu SetPlayerPos
ile ayarladığınızda, sunucu hemen ve tüm diğer oyuncular için içsel olarak konumlarını güncellemez. Bunun yerine, yeni konuma yönlendirilen tek bir oyuncuya bir SET POSITION
komutu gönderilir, yeni konumlarına ışınlanır ve ardından bir sonraki senkronizasyon paketinde sunucuya yeni konumunu bildirirler. Bunun nedeni basitçe gecikmedir - yeni konumu ayarladıktan sonra eski konumla ilgili hala bir veya daha fazla senkronizasyon paketi olabilir. Peki bu, sahte oyuncular için ne anlama geliyor? Bu, aslında konumlarını ayarlayamayacağınızları anlamına gelir, en azından normal yolla değil. Gerçek bir istemci olmadığı için (yani, konum ayarlama komutunu alan) hiçbir şey yoktur ve bu veriyi tekrar sunucuya senkronize eden bir şey de yoktur.
Sunucu için herhangi bir sahte senkronizasyon verisi oluşturabiliriz, bu yolla konumlarını güncellemenin bir yolu, ancak sadece bir yaygın işlem için biraz sıkıcı olabilir, bu nedenle tam olarak bu tek kullanım durumu için Mock_SetPlayerPos
bulunmaktadır. Ancak bu hala tam hikaye değil, çünkü akış oluşturucunun çalışmasına ve yeni konumlarıyla güncellenmesine ihtiyacımız var, bu da yalnızca periyodik olarak gerçekleşir (sunucu tick oranına bağlı olarak birkaç milisaniyede bir). Bu işlem için başka bir sahte işlevimiz var - Mock_Tick
, sunucu zamanını belirli bir miktar mikrosaniye (MİLİSANİYE DEĞİL) ileri atlatabilen bir işlevdir. Bu, canlı sunucularda kesinlikle kullanılmamalıdır, çünkü zamanlayıcılar ve diğer zaman hassas kodlarla garip durumlar yaratabilir, ancak testlerde zamanın geçmesini taklit etmek için kullanılabilir. Mock_Tick(10000)
ifadesi 10 milisaniye gecikmeye neden olmaz, bunun yerine zamanı anında 10 milisaniye ileri alır.
TEST__ SetPlayerCheckpoint()
{
new playerid = Mock_CreatePlayer("Mr Mock");
// Mevcut tüm paketleri temizle, arama işlemini kolaylaştır.
Mock_PurgeSent(playerid);
// Nesne Oluştur
CreateObject(1337, 100.0, 100.0, 4.0);
// Sahte oyuncunun sunucuya, nesneye yakın olarak konum güncellemesini taklit et.
Mock_SetPlayerPos(playerid, 105.0, 105.0, 4.0);
// Şimdi SAHTE bir süre geçir, böylece akış oluşturucu çalışabilir (50 ms uygun olacaktır).
Mock_Tick(50000);
// Bir "CreateObject" paketinin görüldüğünü kontrol et.
new MockPacket:packet = Mock_GetPacket(playerid, "CreateObject");
ASSERT(packet);
if (!packet) return;
// Paketin 4 bileşen içerdiğini ve her birinin 32 bit olduğunu kontrol et.
ASSERT(MockPacket_Components(packet) > 5);
// Paketin bazı bileşenlerini kontrol et.
new int;
new Float:tmp;
ASSERT(MockPacket_ReadInt32(packet, 1, int));
ASSERT(int == 1337);
ASSERT(MockPacket_ReadFloat(packet, 2, tmp));
ASSERT(tmp == 100.0);
ASSERT(MockPacket_ReadFloat(packet, 3, tmp));
ASSERT(tmp == 100.0);
ASSERT(MockPacket_ReadFloat(packet, 4, tmp));
ASSERT(tmp == 4.0);
}
Mock_Tick
hakkında önemli bir not: Diyelim ki Mock_Tick(100000)
gibi çağırırsanız ve çalışan bir 1 ms zamanlayıcınız varsa, bu zamanlayıcıyı anında çalıştıracak, ancak zamanlayıcı kendisini uzun bir mesafe boyunca (100 ms) kaçırdığını düşünecek ve tekrar sıraya girecektir. Mock_Tick
çağrısı sırasında zamanlayıcı bir kez ateşlenecek, ancak mevcut test bittiğinde zamanlayıcı 100 kez daha sıraya alınacaktır, çünkü 100 çağrı süresini kaçırdığını düşünüyor.
Daha sonra, sahte paketleri tek tek yazıp okumanızı daha kolay hale getirmek için bazı daha yüksek düzeyli hata ayıklama tekniklerini ele alacağız, ancak bu temelde her şeyin nasıl inşa edildiğini tanıtır. Umarım bu, herkes için kod geliştirmeyi ve test etmeyi daha kolay hale getirir (ve aynı zamanda sunucunun daha kararlı olmasını sağlar).
Gelecek sefer open.mp için API tasarımına çok daha derinlemesine gireceğiz. Bu, SA:MP ile tamamen uyumludur, ancak bu, eşzamanlı bir iyileştirme için paralel bir alan olmadığı anlamına gelmez.