Skip to content

닫힌 세계

결정론적인 게임플레이 프로그래밍을 위해서는 닫힌 세계에 대하여 이해하여야합니다.

내부 상태 관리

컨테이너

컨테이너 혹은 컬렉션은 구현 레벨에서 데이터를 순회할 때 순서를 보장하는지 여부를 기준으로 나눌 수 있습니다.
여기서 순서를 보장한다는 것은 입력한 순서대로 가져올 수 있는지 여부를 말하는 것이 아닙니다.
같은 데이터를 가지고 있는 컨테이너들끼리는 같은 순서로 데이터를 순회할 수 있어야한다는 것입니다. 순서를 보장하는 System.Collections.Generic.List<T>, std::vector<T>와 같은 컨테이너들은 그냥 평소와 같이 사용하면 됩니다.

신경써야할 컨테이너들은 순서를 보장하지 않는 컨테이너들입니다. 대표적으로 System.Collection.Generic.Dictionary<TKey, TValue>, std::unordered_map<>이 있습니다. 주로 해시 값을 기반으로 하는 컨테이너들인데 이론상 O(1) 시간에 삽입, 삭제, 탐색을 할 수 있어서 자주 사용합니다. std::unordered_map<>은 이름에서도 어필하듯 순서를 보장하지 않습니다.

이론상 해시 함수는 같은 데이터에 대하여 동일한 해시 값을 반환해야합니다. 하지만 C#이나 Java와 같은 언어에서의 class들은 별도로 해시 함수를 정의하지 않으면 객체의 ID에 해당하는 값을 반환합니다. 이는 사실상 무작위에 가깝습니다.

그리고 보안 문제로 같은 데이터에 대하여 프로세스, 빌드에 따라 다른 해시 값을 반환하는 런타임도 있습니다. System.String.GetHashCode()의 구현을 보면 디스크와 같은 저장소에 해시 값을 보관하지 말라는 주석이 달려있습니다.

해시 값이 달라지면 데이터가 보관되는 버킷이 달라지는건 말할 것도 없고, 만일 해시 값이 같더라도 버킷 크기에 따라 해시 충돌로 저장되는 버킷이 달라질 수 있습니다. 버킷이 달라지면 같은 데이터를 보관하는 컨테이너들이라도 같은 순서로 데이터를 가져올 수 있으리라는 보장을 할 수 없습니다.

결정론적인 게임플레이 코드에서 순서를 보장하지 않는 컨테이너들을 순회할 때는 명시적으로 정렬한 후 순회해야합니다.

var names = new HashSet<string>() { "John", "Bob", "Halak" };
foreach (var name in names.OrderBy(name => name, StringComparer.Ordinal))
{
    // ...
}

외부 입력 처리

작성 중