11. 순서보장 채널 + 비순서 채널 혼용 시 상태 정합성 (C#)

난이도 최상 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 순서보장 채널 + 비순서 채널 혼용 시 상태 정합성
// ----------------------------------------------------------------------------
// 시나리오:
//   - 게임은 두 종류의 채널을 함께 쓴다.
//       * 신뢰·순서보장 채널(TCP류): 스폰/디스폰, 장비 변경, 사망 등 "중요 이벤트".
//       * 비신뢰·비순서 채널(UDP류): 좌표 스냅샷(PosUpdate)처럼 고빈도·유실 허용.
//     두 채널은 서로 독립적이라, 채널 간 도착 순서는 보장되지 않는다(같은 채널
//     안에서도 비순서 채널은 재정렬/유실/중복 가능).
//   - 각 패킷 와이어: [ushort seq][byte type][payload], seq 는 채널별로 1씩 증가.
//   - 클라이언트는 수신한 패킷을 엔티티 뷰(_entities)에 반영한다.
//   - 서버는 좌표를 비신뢰 채널로, 스폰/디스폰/사망을 신뢰 채널로 보낸다.
//
// 요구사항:
//   - 늦게/뒤늦게 도착한 옛 좌표 스냅샷이 최신 좌표를 덮어쓰면 안 된다.
//   - 채널 간 인과관계(스폰 → 그 엔티티의 좌표)가 깨져도 유령/누락 엔티티가
//     생기면 안 된다.
//   - 사망처럼 한 번 놓치면 상태가 영구히 어긋나는 이벤트는 유실되면 안 된다.
//   - seq 는 ushort 라 65536 마다 한 바퀴 돈다(wrap).
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 정합성이 깨지는지 설명하고,
//   수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System.Collections.Generic;

public enum MsgType : byte { Spawn = 1, Despawn = 2, Pos = 3, Death = 4 }

public struct Payload
{
    public uint  EntityId;
    public float X, Y;
}

public class EntityView
{
    public uint  Id;
    public float X, Y;
    public bool  Alive;
}

public class ClientWorld
{
    private readonly Dictionary<uint, EntityView> _entities = new();
    private ushort _lastSeq = 0;   // (E) 모든 엔티티/타입 공통 last seq

    // 신뢰·순서보장 채널 수신 (TCP류) — 채널 내 순서는 보장됨
    public void OnReliable(ushort seq, MsgType type, Payload p)
    {
        switch (type)
        {
            case MsgType.Spawn:
                _entities[p.EntityId] = new EntityView
                    { Id = p.EntityId, X = p.X, Y = p.Y, Alive = true };
                break;
            case MsgType.Despawn:
                _entities.Remove(p.EntityId);
                break;
        }
    }

    // 비신뢰·비순서 채널 수신 (UDP류) — 재정렬/유실/중복 가능
    public void OnUnreliable(ushort seq, MsgType type, Payload p)
    {
        switch (type)
        {
            case MsgType.Pos:
                // (A) 전역 last seq 로만 최신 여부 판단
                if (seq <= _lastSeq)            // (B) wrap 미고려 비교
                    return;
                _lastSeq = seq;

                // (C) 엔티티가 없어도 좌표로 새로 만든다
                var e = _entities.TryGetValue(p.EntityId, out var ev)
                        ? ev : (_entities[p.EntityId] = new EntityView { Id = p.EntityId });
                e.X = p.X;
                e.Y = p.Y;
                break;

            case MsgType.Death:                 // (D) 중요한 이벤트가 비신뢰 채널로
                if (_entities.TryGetValue(p.EntityId, out var d))
                    d.Alive = false;
                break;
        }
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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