[SourceMod] 소스모드 1.7.0 부터 추가된 문법에 관해

Posted by subkarsei2
2015. 1. 23. 14:32 Info/Tips/Programming



현재 제가 쓰고 있는 시점으로는 1.6.3 버전이 stable 버전으로 나와있습니다만, 개발버전으로는 1.7.0으로 나와있는데 1.7.0 버전부터 새로운 문법을 사용할 수 있습니다.


이 새로운 문법은 Transitional API 라고 Pawn 언어를 좀 더 현대적이면서도 개발자들에게 객체지향 방식으로 호환성을 유지한채 기존 API들을 사용할 수 있게 해줄 수 있습니다. 따라서 이전에 작성했던 코드를 이번에 새로 생긴 문법에 맞출 필요는 없습니다.


현재 1.7.0 버전은 개발중이기 때문에 수정될 때마다 글에 추가하겠습니다.


1.7.0 기준 API 참고 주소가 새롭게 나왔습니다. - https://sm.alliedmods.net/new-api/


이번에 생겨난 문법은 크게 4가지의 기능을 가지고 변경되었습니다.


  • 새로운 선언 방식 - 마치 Java 나 C# 비슷하게 변수를 깨끗하게 선언할 수 있습니다.
  • Methodmaps - '메소드맵'이라고 부르는데, 기존 API를 객체지향 방식으로 감싸줄 수 있습니다.
  • 실제 자료형 타입 - 자료형 타입으로 'int', 'float', 'bool', 'void', 'char' 를 쓸 수 있습니다.
  • null - 기존의 INVALID_HANDLE 키워드를 이제 null 로 쓸 수 있습니다.



선언 방식


기존에 선언하던 방식은 다음과 같습니다.


new Float:yeah = 3.14;

new chknum;


새로운 문법으로는 다음과 같이 쓸 수 있습니다.


float yeah = 3.14;

int chknum;


아래의 변경 이유는 AM 개발자가 말한 내용입니다.




배열


배열의 경우는 하나의 방법이 추가되어 두 가지의 방법을 쓸 수 있습니다.

  • 크기가 명확한 고정 크기 배열(fixed-length)
  • 크기가 모호한 동적 크기 배열(dynamic-length)


크기를 알 수 있는 고정 크기 배열 방법은 다음과 같이 변수 이름 뒤에 괄호를 만들어 크기를 선언하는 식입니다. 크기를 쓰지 않아도 내용 안에 명확한 내용물이 있다면 배열 크기가 고정됩니다.


int g_iItemEnv[200];

int g_iPlayerData[MAXPLAYERS + 1] = {0, ...};

int g_iEnvSet[] = {ENV_NUM1, ENV_NUM2};



이번에 추가된 크기를 알 수 없는 동적 크기 배열 방법은 변수 앞, 자료형 타입 뒤에 괄호를 써서 선언합니다.


void IamExample()

{

     int[] players = new int[MaxClients + 1];

}


위의 방식을 이용하여 players 라는 배열을 선언해보았습니다. 이 경우 메모리는 저 변수를 사용하지 않을 때 자동적으로 비어집니다.


동적 크기 배열과 같이 크기가 모호한 배열로 고정 크기 배열로 초기화하는 짓, 고정 크기 배열로 동적 배열로 초기화하는 짓, 동적 크기 배열에 크기를 정해놓는 짓은 잘못된 방식입니다.



위의 방식을 다음과 같이 쓸 수도 있습니다.


void YeahFunction(int client, const char[] data)

{

     //

}



View As


다른 형태의 타입으로 값을 해석해야할 경우가 생겨 'view_as' 라는 새로운 연산자가 나왔습니다. 이 연산자는 실제 값이 다른 것으로 바뀌지 않으면서 다른 형태의 타입으로 바꿀 수 있습니다.


다음과 같이 사용할 수 있습니다.


void YeahDataLoad(int client, any data)

{

     Handle yo = view_as<Handle>(data);

}



메소드맵(MethodMaps)


이번에 제가 정말 마음에 들었던 기능입니다. 마치 클래스 뺨치는 기능(생성자, 소멸자, 게터와 세터, 상속 등)을 제공합니다.

메소드맵은 말그대로 메소드 만을 가지고 다루는 하나의 맵입니다. 따라서 위와 같은 뺨치는 기능을 제공하더라도 다른 언어에서 말하는 '클래스'가 아닙니다(https://forums.alliedmods.net/showpost.php?p=2176262&postcount=77). 멤버변수조차 선언할 수 없으니 주의해야 합니다.


소스모드에 등록되어 있는 API를 가지고 설명을 해보겠습니다.


native CloseHandle(Handle hndl);


위의 함수는 많이 사용하고 봐오던 그저 일반적인 API 입니다. 이것을 다른 코드와 같이 이용해서 뭔가 하나의 작업을 만들어보겠습니다.


Handle hTempArr = CreateArray(8);

PushArrayCell(hTempArr, 123);

CloseHandle(hTempArr);


일단 코드 자체는 쓸모없는 작업입니다만.. 예시를 위해서 작성해보았습니다.

이걸 메소드맵을 이용해서 코드를 변경해보겠습니다.


우선 맨 위에 있는 CloseHandle을 묶어보겠습니다. 다음과 같이 깔끔하게 정리 가능합니다.


native CloseHandle(Handle:hndl);


methodmap Handle

{

     public Close() = CloseHandle;

};


소멸자를 한번 이용해보겠습니다.


native CloseHandle(Handle:hndl);


methodmap Handle

{

     public Close() = CloseHandle;

     public ~Handle() = CloseHandle;

};


이렇게 간단하게 묶어주면서 소멸자로도 사용이 가능합니다.

자 이제 아까전에 잠시 만들었던 작업 코드를 가지고 바꿔보겠습니다.


일단 ArrayList 라는 이름으로 메소드맵을 만들고 방금 위에서 만든 Handle 메소드맵을 상속도 해서 아래와 같은 코드를 만들어보았습니다.


native CloseHandle(Handle:hndl);


methodmap Handle

{

     public Close() = CloseHandle;

     public ~Handle() = CloseHandle;

};


methodmap ArrayList < Handle

{

     public native void Push(any value);

};


CreateArray() 함수는 ArrayList 라는 메소드맵을 이용하고 반환형으로는 Handle 형태로 반환한다고 가정합니다


ArrayList hTempArr = CreateArray(8);

hTempArr.Push(123);

htempArr.Close();


말했듯이 CreateArray() 함수는 ArrayList 메소드맵을 이용하고 반환형으로는 Handle 형태를 가진 객체로 반환합니다.


그래서 'hTempArr' 라는 변수는 ArrayList 라는 메소드맵을 이용하면서 Handle 형태를 가진 객체가 됩니다.

그러므로 ArrayList 메소드맵 안에 있는 Push 함수를 쓸 수 있고, 상속도 하였으니 Close 함수를 쓸 수 있습니다.



위에서 잠깐 선보였지만 생성자와 소멸자는 다음과 같이 선언하면 됩니다.


methodmap yeahExample

{

     public yeahExample(); // 생성자

     public ~yeahExample(); // 소멸자

}


게터와 세터는 다음과 같이 선언하면 됩니다.


methodmap playerData

{

     property int GetClass

     {

          public native get();

          public native set(int iClass);

     }

}


자기 자신을 나타낼 수도 있습니다.


methodmap playerData

{

     property int ClassSize

     {

          public get() = GetSomeExmClassSize;

     }

     property bool IsEmpty

     {

          public get()

          {

               return this.ClassSize == 0;

          }

     }

}




추가적으로 변경된 사항은 다음과 같습니다.


  • 함수 타입을 강제로 하는 것은 warning 뜨게 됩니다. 예를 들면 함수에 any같은 태그를 붙여서 하는 경우(public any:abcde) 뭐 이 경우에는 int 로써 반환되는데, SourcePawn 언어를 향상시키는데 있어서 심각한 방해물로 취급하여 향후에는 오류가 뜬다고 합니다.
  • decl 키워드는 더 이상 지원하지 않습니다.
  • 다중 태그 지원이 삭제되었습니다.
  • sizeof 연산자가 불명확한 배열에는 더 이상 쓰여지지 않습니다.
  • 향후 지원할 예약어가 추가되었습니다: acquire, as, builtin, catch, cast_to, double, explicit, finally, foreach, implicit, import, in, int8, int16, int32, int64, intn, let, namespace, object, package, private, protected, readonly, sealed, throw, try, typeof, uint8, uint16, uint32, uint64, uintn, union, var, variant, volatile, with
  • sizeof 연산자가 기본 파라메터 표현에 쓰여서 사용하는 것이 삭제되었습니다.
  • String[] 과 any 가 더 이상 강제적인 사항이 아니게 되었습니다.
  • int 와 void 를 태그로서 사용하는 것은 warning 이 뜨게 되었습니다.
  • enum을 사용할때 재태그(retagging)할 수 없습니다.
  • implicit-int 태그가 enum 태그로서 더 이상 사용될 수 없습니다.



다시 말하지만, 꼭 이번에 새로 추가된 문법으로 맞출 필요없습니다. 혼용해서 써도 상관없습니다.