45. 존 경계 핸드오프의 비원자 이동과 소속 붕괴 (C#)

난이도 중 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// 시나리오: 심리스(seamless) 오픈월드 MMORPG. 월드는 여러 개의 존(Zone)으로 나뉘고,
// 각 존은 자신의 관리 리스트에 소속 플레이어를 담아 AoI(관심영역) 브로드캐스트를 돌린다.
// 플레이어가 존 경계를 넘으면 이전 존에서 빠지고 다음 존으로 들어가는 "핸드오프"가 일어난다.
//
// 요구사항:
//  - 핸드오프가 끝난 시점에 플레이어는 정확히 하나의 존에만 소속되어야 한다.
//  - 이동 처리와 브로드캐스트는 서로 다른 스레드(이동 워커 / 존 틱 워커)에서 동시에 돈다.
//  - 경계를 빠르게 왕복하거나 여러 이동 패킷이 몰려도 소속 상태가 깨지지 않아야 한다.
//
// 과제: MovePlayer 와 관련 코드를 검토하고 동시성 관점에서 무엇이 잘못될 수 있는지,
//       어떤 조건에서 재현되는지, 어떻게 고칠지 설명하라. (A)(B) 지점을 특히 살펴보라.

using System.Collections.Generic;

public sealed class Zone
{
    public int Id;
    public readonly List<Player> Members = new List<Player>();

    public void Add(Player p) { Members.Add(p); }
    public void Remove(Player p) { Members.Remove(p); }

    // 존 틱 워커 스레드에서 주기적으로 호출된다.
    public void BroadcastTick()
    {
        foreach (var p in Members)          // (B)
            p.SendAoISnapshot(Members);
    }
}

public sealed class Player
{
    public int Id;
    public Zone CurrentZone;
    public void SendAoISnapshot(List<Player> around) { /* 직렬화·송신 */ }
}

public sealed class World
{
    private readonly Dictionary<(int, int), Zone> _grid = new Dictionary<(int, int), Zone>();

    public Zone ZoneAt(float x, float y) => _grid[((int)x / 128, (int)y / 128)];

    // 이동 워커 스레드에서 플레이어의 위치 패킷마다 호출된다.
    public void MovePlayer(Player p, float x, float y)
    {
        Zone from = p.CurrentZone;
        Zone to = ZoneAt(x, y);
        if (from == to) return;

        from.Remove(p);          // (A)
        to.Add(p);
        p.CurrentZone = to;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.