31. 엔티티 핸들(slot map)의 세대 검증 부재·비동기 재사용으로 인한 오접근(ABA형) (C#)

난이도 최상 해설 보기 →

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

결함 코드 · C#
// 시나리오: 월드 엔티티를 슬롯맵(slot map)으로 관리한다. 엔티티는 _slots 배열에 저장되고,
// 외부 코드는 핸들 Handle{Index, Generation} 로 엔티티를 참조한다. 엔티티가 디스폰되면
// 슬롯은 비워지고 Generation 이 증가하며, 그 슬롯은 다음 스폰에 재사용된다.
// 스폰/디스폰은 스폰·디스폰 이벤트 스레드가, Resolve() 는 게임플레이/네트워크 스레드가 호출한다.
//
// 요구사항:
//   - 오래된(stale) 핸들 — 가리키던 엔티티가 이미 디스폰된 핸들 — 로 Resolve 하면
//     반드시 null 을 반환해야 한다(다른 엔티티를 돌려주면 안 됨).
//   - Resolve 는 스폰/디스폰과 동시에 호출될 수 있다.
//
// 과제: (A)(B) 지점을 검토하라. 특정 순서에서 Resolve 가 의도와 다른 결과를 주거나
//       일관되지 않은 상태를 읽을 수 있다. 어떤 순서에서 그런지 밝히고 고쳐라.

using System.Collections.Generic;

public sealed class Entity { public int Hp; public float X, Y; }
public struct Handle { public uint Index; public uint Generation; }

public sealed class EntityRegistry
{
    private struct Slot { public uint Generation; public bool Alive; public Entity Entity; }

    private readonly Slot[] _slots;
    private readonly Stack<uint> _freeList = new();

    public EntityRegistry(uint capacity)
    {
        _slots = new Slot[capacity];
        for (uint i = capacity; i-- > 0; ) _freeList.Push(i);
    }

    public Handle Spawn()                          // 스폰 이벤트 스레드
    {
        uint idx = _freeList.Pop();
        _slots[idx].Alive  = true;
        _slots[idx].Entity = new Entity();
        return new Handle { Index = idx, Generation = _slots[idx].Generation };
    }

    public void Despawn(Handle h)                  // 디스폰 이벤트 스레드
    {
        _slots[h.Index].Alive = false;
        _slots[h.Index].Generation++;              // 재사용을 위해 세대 증가
        _freeList.Push(h.Index);
    }

    public Entity? Resolve(Handle h)               // 게임플레이/네트워크 스레드
    {
        ref Slot s = ref _slots[h.Index];
        if (s.Alive)                               // (A)
            return s.Entity;                       // (B)
        return null;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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