17. 관심영역(AoI) 진입/이탈 시 엔티티 동기화 경합
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 관심영역(AoI) 진입/이탈 시 엔티티 동기화 경합
// ----------------------------------------------------------------------------
// 시나리오:
// - 심리스 월드에서 각 관찰자(플레이어)는 자기 주변 일정 반경(AoI)의 엔티티만
// 본다. 서버는 관찰자별로 "현재 보이는 엔티티 집합(_visible)"을 관리한다.
// - 주변 엔티티가 움직이면 이동 워커 스레드가 Update(near) 를 호출해, 새로 시야에
// 들어온 엔티티에는 Spawn 패킷을, 시야를 벗어난 엔티티에는 Despawn 패킷을 보낸다.
// - 동시에 다른 스레드는 엔티티 접속종료/사망 시 OnEntityRemoved 를 호출해 시야에서
// 제거한다. 여러 엔티티가 동시에 움직이므로 같은 관찰자의 뷰가 동시 갱신될 수 있다.
//
// 요구사항:
// - 이미 보이는 엔티티에 Spawn 을 두 번 보내면 안 되고, 안 보이는 엔티티에 Despawn 을
// 중복으로 보내면 안 된다(Spawn/Despawn 짝 맞춤).
// - 시야에서 사라진(또는 제거된) 엔티티가 계속 보이는 "유령" 이 없어야 한다.
// - 동시 갱신에도 집합 자료구조가 손상되거나 정의되지 않은 동작이 나면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 깨지는지(동시 인터리빙 포함)
// 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
public class Entity
{
public int Id;
public float X, Y;
public bool Alive = true;
}
public interface IClient
{
void SendSpawn(Entity e);
void SendDespawn(int entityId);
}
public class AoIView
{
// 이 관찰자가 현재 보고 있는 엔티티 ID 집합
private readonly HashSet<int> _visible = new();
private readonly IClient _client;
public AoIView(IClient client) { _client = client; }
// 이동 워커가 주변 후보 목록(near)으로 시야를 갱신한다.
public void Update(IReadOnlyList<Entity> near)
{
// 1) 새로 보이는 것 Spawn
foreach (var e in near)
{
// (A)
if (!_visible.Contains(e.Id))
{
_visible.Add(e.Id);
_client.SendSpawn(e);
}
}
// 2) 더 이상 안 보이는 것 Despawn
// (B)
foreach (var id in _visible)
{
if (!ContainsId(near, id))
{
_visible.Remove(id); // 순회 중 컬렉션 수정
_client.SendDespawn(id);
}
}
}
// 엔티티가 월드에서 제거(접속종료/사망)될 때 호출
public void OnEntityRemoved(Entity e)
{
// (C)
if (_visible.Contains(e.Id))
{
_visible.Remove(e.Id);
_client.SendDespawn(e.Id);
}
}
private static bool ContainsId(IReadOnlyList<Entity> near, int id)
{
foreach (var e in near)
if (e.Id == id) return true;
return false;
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 관심영역(AoI) 진입/이탈 시 엔티티 동기화 경합
// ----------------------------------------------------------------------------
// 시나리오:
// - 심리스 월드에서 각 관찰자(플레이어)는 자기 주변 일정 반경(AoI)의 엔티티만
// 본다. 서버는 관찰자별로 "현재 보이는 엔티티 집합(visible_)"을 관리한다.
// - 주변 엔티티가 움직이면 이동 워커 스레드가 update(near) 를 호출해, 새로 시야에
// 들어온 엔티티에는 Spawn 패킷을, 시야를 벗어난 엔티티에는 Despawn 패킷을 보낸다.
// - 동시에 다른 스레드는 엔티티 접속종료/사망 시 onEntityRemoved 를 호출해 시야에서
// 제거한다. 여러 엔티티가 동시에 움직이므로 같은 관찰자의 뷰가 동시 갱신될 수 있다.
//
// 요구사항:
// - 이미 보이는 엔티티에 Spawn 을 두 번 보내면 안 되고, 안 보이는 엔티티에 Despawn 을
// 중복으로 보내면 안 된다(Spawn/Despawn 짝 맞춤).
// - 시야에서 사라진(또는 제거된) 엔티티가 계속 보이는 "유령" 이 없어야 한다.
// - 동시 갱신에도 집합/포인터가 손상되거나 정의되지 않은 동작이 나면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 깨지는지(동시 인터리빙·수명 포함)
// 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <unordered_set>
#include <vector>
#include <memory>
#include <cstdint>
struct Entity {
int id;
float x, y;
bool alive = true;
};
class IClient {
public:
virtual ~IClient() = default;
virtual void sendSpawn(const Entity& e) = 0;
virtual void sendDespawn(int entityId) = 0;
};
class AoIView {
public:
explicit AoIView(IClient* client) : client_(client) {}
// 이동 워커가 주변 후보 목록(near)으로 시야를 갱신한다.
void update(const std::vector<Entity*>& near) {
// 1) 새로 보이는 것 Spawn
for (Entity* e : near) {
// (A)
if (visible_.find(e->id) == visible_.end()) {
visible_.insert(e->id);
client_->sendSpawn(*e);
}
}
// 2) 더 이상 안 보이는 것 Despawn
// (B)
for (auto it = visible_.begin(); it != visible_.end(); ++it) {
if (!containsId(near, *it)) {
visible_.erase(it); // 순회 중 컨테이너 수정(반복자 무효화)
client_->sendDespawn(*it);
}
}
}
// 엔티티가 월드에서 제거(접속종료/사망)될 때 호출
void onEntityRemoved(Entity* e) {
// (C)
if (visible_.find(e->id) != visible_.end()) {
visible_.erase(e->id);
client_->sendDespawn(e->id);
}
}
private:
static bool containsId(const std::vector<Entity*>& near, int id) {
for (Entity* e : near)
if (e->id == id) return true;
return false;
}
std::unordered_set<int> visible_;
IClient* client_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.