[diffusion-planner] nuplan/common/utils/distributed_scenario_filter.py

ad_official·2025년 3월 4일

diffusion planning

목록 보기
17/19

1. DistributedScenarioFilter 클래스

1.1. GPT 설명

  • 분산 환경에서 시나리오(실행할 시뮬레이션의 대상 데이터)를 효율적으로 분배하고 필터링하는 역할을 수행
  • 특히, get_scenarios 메서드는 현재 노드(또는 머신)가 처리해야 할 시나리오 목록을 결정하는 핵심 함수
  • 이 클래스와 get_scenarios 메서드의 기능을 자세히 살펴보면 다음과 같음

요약

  • DistributedScenarioFilter의 get_scenarios 메서드는 분산 처리 설정에 따라 다음과 같이 동작합니다:

  • 단일 노드 / SINGLE_NODE 모드:

    • 설정에 따라 전체 시나리오를 단순히 추출하여 반환
  • LOG_FILE_BASED 모드:

    • 현재 노드에 할당된 로그 DB 파일로부터 시나리오들을 추출하여 그대로 반환
  • SCENARIO_BASED 모드:

    • 로그 파일로부터 시나리오들을 추출한 후,
    • 모든 노드의 결과를 동기화하고 재분배하여
      • 현재 노드에 할당된 시나리오 토큰을 반영한 조건으로 시나리오 빌더를 다시 생성하여 최종 시나리오 목록을 반환
  • 이 과정은 분산 환경에서 전체 시나리오 작업 부하를 균등하게 나누고, 각 노드가 올바른 데이터만 처리할 수 있도록 보장합니다.


1. DistributedScenarioFilter 클래스의 전체 역할

  • 분산 작업 분할:
    여러 노드(또는 머신)가 동시에 시뮬레이션을 실행할 때, 전체 시나리오 목록을 각 노드에 균등하게 나누어 할당합니다.

  • 동기화:

    • 각 노드가 개별적으로 시나리오를 추출한 후,
    • 동기화(synchronization)를 통해 모든 노드의 작업 결과를 모으고,
    • 이를 기반으로 재분배(리파티셔닝)를 수행할 수 있도록 지원합니다.
  • S3 또는 로컬 파일 시스템 지원:
    분산 환경이 S3와 같은 클라우드 스토리지를 사용하는 경우와 로컬 파일 시스템을 사용하는 경우 모두를 지원합니다.
    예를 들어, CSV 파일을 읽어 전체 시나리오 토큰(token)과 로그 파일 정보를 수집합니다.


2. get_scenarios 메서드의 동작 흐름

  • get_scenarios 메서드는 현재 노드가 처리할 시나리오들을 결정하여 반환합니다.
  • 동작 방식은 크게 세 가지 경우로 나눌 수 있습니다.

A. 단일 노드 또는 SINGLE_NODE 모드

  • 조건:
    전체 노드 수가 1이거나, 분산 모드가 SINGLE_NODE로 설정된 경우
  • 동작:
    • 바로 build_scenario_builder(cfg)build_scenario_filter(cfg.scenario_filter) 함수를 호출하여, 설정(cfg)에 정의된 모든 시나리오를 한 번에 추출합니다.
    • scenario_builder.get_scenarios(scenario_filter, self._worker)를 호출하여 필터에 맞는 시나리오들을 가져옵니다.
  • 결과:
    모든 시나리오를 그대로 반환합니다.

B. LOG_FILE_BASED 모드

  • 조건:
    분산 모드가 LOG_FILE_BASED인 경우
  • 동작:
    • 먼저, _get_log_db_files_for_single_node()를 호출하여 현재 노드에 할당된 로그 DB 파일 목록(시나리오의 원천 데이터 청크)을 얻습니다.
    • _get_scenarios_from_list_of_log_files(current_chunk)를 통해, 해당 로그 파일들에서 시나리오들을 추출합니다.
    • LOG_FILE_BASED 모드에서는 추가 재분배 없이, 해당 노드의 로그 파일에서 추출한 시나리오들을 그대로 반환합니다.
  • 결과:
    현재 노드에 속한 로그 파일로부터 추출한 시나리오 목록을 반환합니다.

C. SCENARIO_BASED 모드

  • 조건:
    분산 모드가 SCENARIO_BASED인 경우
  • 동작:
    1. 시나리오 추출:
      • LOG_FILE_BASED 모드와 같이, 먼저 _get_log_db_files_for_single_node()를 호출하여 현재 노드의 로그 파일 청크를 가져온 후,
      • _get_scenarios_from_list_of_log_files(current_chunk)를 통해 시나리오들을 추출합니다.
    2. 재분배(리파티셔닝):
      • _get_repartition_tokens(scenarios)를 호출하여, 현재 노드에서 추출한 시나리오들의 토큰(token)과 해당 로그 파일 정보를 다른 노드들과 동기화합니다.
        • 이 과정에서는 각 노드가 CSV 파일을 생성하고, 동기화(예: distributed_sync)를 통해 전체 노드의 토큰 목록을 모읍니다.
        • 이후, 전체 토큰 목록을 균등하게 나누어 현재 노드가 처리할 시나리오 토큰과 관련 DB 파일 목록을 결정합니다.
    3. 설정 업데이트 및 재추출:
      • 동기화 후, 설정(cfg)의 scenario_filter.scenario_tokensscenario_builder.db_files를 새로 업데이트하여, 재분배된 토큰과 DB 파일 목록을 반영합니다.
      • 그리고 나서 다시 build_scenario_builder(cfg)build_scenario_filter(cfg.scenario_filter)를 호출하여, 재분배된 조건에 맞게 시나리오 빌더와 필터를 새로 생성합니다.
  • 결과:
    재분배된 토큰에 해당하는 시나리오들을 최종적으로 추출하여 반환합니다.

3. 주요 헬퍼 메서드

  • _get_log_db_files_for_single_node():
    현재 노드에 할당된 로그 DB 파일 목록을 결정합니다.
    단일 노드이면 설정에서 그대로 가져오고, 다중 노드인 경우 S3에서 모든 로그 파일 목록을 청크(chunk)로 나눠 해당 청크를 반환합니다.

  • _write_token_csv_file() & _get_all_generated_csv():
    각 노드가 처리한 시나리오의 토큰과 로그 파일 이름을 CSV 파일로 저장한 후, 모든 노드의 CSV 파일을 모아 전체 토큰 목록을 구성합니다.

  • _get_token_and_log_chunk_on_single_node():
    전체 토큰 목록을 여러 노드로 균등하게 분할하고, 현재 노드에 해당하는 토큰과 관련 로그 파일 목록을 결정합니다.

  • _get_scenarios_from_list_of_log_files():
    지정된 로그 파일 목록을 기반으로 시나리오 빌더와 필터를 사용하여 시나리오들을 추출합니다.


2. build_scenario_builder 함수

  • nuplan/planning/script/builders/scenario_building_builder.py 에 있음

2.1 GPT의 설명

  • 이 함수는 Hydra의 DictConfig 객체로부터 시나리오 빌더(Scenario Builder) 인스턴스를 생성하는 역할

요약

  • 이 함수는 cfg.scenario_builder 에 정의된 내용을 기반으로 시나리오 빌더를 자동으로 생성
  • 예시 설정에서는 차량 물리 파라미터, 시나리오 매핑 정보, 데이터/지도/센서 파일 경로 등 다양한 정보가 포함되어 있어,
    • NuPlanScenarioBuilder 가 올바르게 초기화되고 시나리오를 구성할 수 있게 됨
      • nuplan.planning.scenario_builder.nuplan_db.nuplan_scenario_builder.NuPlanScenarioBuilder
  • 이러한 과정을 통해, 나중에 시뮬레이션이나 데이터 처리를 위한 시나리오 생성에 필요한 모든 정보와 기능을 갖춘 시나리오 빌더 객체를 얻을 수 있게 됩니다.

2. cfg.scenario_builder 설정이 주입되면 어떻게 동작하는가?

주석에 나온 예시 설정(cfg.scenario_builder)은 다음과 같이 구성되어 있습니다:

  • vehicle_parameters:

    • _target_: 'nuplan.common.actor_state.vehicle_parameters.VehicleParameters'
    • 차량의 물리적 특성(너비, 길이, 높이 등)이 설정되어 있습니다.
      → 이 부분은 시나리오 빌더가 시나리오 내의 차량(ego 차량 등)의 파라미터를 올바르게 이해하고, 물리적 모델이나 충돌 계산 등에서 활용할 수 있도록 합니다.
  • scenario_mapping:

    • _target_: 'nuplan.planning.scenario_builder.nuplan_db.nuplan_scenario_utils.ScenarioMapping'
    • 시나리오 유형(예: 'accelerating_at_crosswalk', 'changing_lane', 'on_intersection' 등)과 각 시나리오에 해당하는 파라미터([15.0, -3.0]와 같은 값들)가 정의되어 있습니다.
      → 이는 시나리오 빌더가 다양한 시나리오를 분류하고 매핑할 때, 각 시나리오 유형에 대해 어떤 기준과 파라미터를 사용할지를 지정합니다.
  • 기타 경로 설정:

    • data_root, map_root, sensor_root 등이 설정되어 있어, 실제 데이터, 지도, 센서 블롭이 저장된 위치를 지정
    • db_files는 (보통 나중에 주입되거나 동적으로 설정되지만) 여기서는 None으로 설정되어 있습니다.
    • map_version, include_cameras, max_workers, verbose 등의 옵션도 포함되어 있습니다.
      → 이 정보들은 시나리오 빌더가 실제 데이터를 불러오고, 시나리오를 생성할 때 필요한 모든 경로와 옵션들을 제공합니다.
  • target 필드:

    • _target_ 값으로 'nuplan.planning.scenario_builder.nuplan_db.nuplan_scenario_builder.NuPlanScenarioBuilder'가 지정되어 있습니다.
      → 이 값은 Hydra가 어떤 클래스를 인스턴스화할지를 결정합니다. 즉, 위 설정을 사용하면 NuPlanScenarioBuilder 클래스의 인스턴스가 생성됩니다.
  • convert 필드:

    • _convert_: 'all'은 DictConfig 내 모든 값들을 필요한 타입으로 변환하도록 지정합니다.

2.2. 구체적인 설명

  • 이 코드를 실행하면 Hydra가 cfg.scenario_builder에 정의된 설정을 읽어,
    NuPlanScenarioBuilder 클래스의 인스턴스를 생성
  • 이 인스턴스는 nuPlan 데이터셋에서 시나리오를 추출하고 구성하는 역할
  • 구체적으로 어떻게 동작하는지, 그리고 내부에 주입되는 VehicleParameters와 ScenarioMapping의 역할은 다음과 같습니다.

결론

  1. Hydra 설정 해석 및 인스턴스 생성:

    • cfg.scenario_builder에 명시된 내용(차량 파라미터, 시나리오 매핑, 데이터 경로 등)을 읽어,
      NuPlanScenarioBuilder 클래스의 인스턴스를 생성
  2. VehicleParameters 주입:

    • 'vehicle_parameters' 부분을 통해, Pacifica 차량에 대한 물리적 특성(너비, 전/후면 길이, 휠베이스 등)이 설정되고,
      이 값들은 시나리오 추출 및 시뮬레이션에서 차량 모델링에 사용됩니다.
  3. ScenarioMapping 주입:

    • 'scenario_mapping' 부분을 통해, 다양한 시나리오 유형에 대한 추출 지침(예: 지속시간 15초, 시작 오프셋 -3초, 샘플링 비율 0.5)이 제공됩니다.
    • 이 정보는 NuPlanScenarioBuilder가 로그 데이터에서 특정 시나리오를 올바른 조건에 맞게 추출하는 데 활용됩니다.
  4. 기타 설정:

    • 데이터, 지도, 센서 데이터의 경로와 map_version, include_cameras 등의 옵션이 설정되어, 전체적으로 시나리오를 구성하는 데 필요한 모든 정보를 NuPlanScenarioBuilder에 전달
  • 최종적으로, 이 빌더 함수는 완전히 구성된 NuPlanScenarioBuilder 인스턴스를 반환하며, 이는 nuPlan 데이터셋으로부터 시나리오를 추출하고, 시뮬레이션이나 학습에 필요한 환경을 준비하는 핵심 컴포넌트로 활용됩니다.

NuPlanScenarioBuilder의 역할 및 기능

  • 데이터 경로 및 자원 관리:
    • data_root, map_root, sensor_root:
      각각 nuPlan 로그 데이터, 지도 데이터, 센서 블롭 데이터의 저장 경로를 지정합니다.
      예를 들어, data_root./data/nuplan-v1.1/test/로 설정되어 있어, 여기서 로그 데이터(DB 파일 등)를 찾습니다.
    • db_files:
      • 데이터베이스 파일 경로를 지정하는데, 여기서는 None으로 되어 있으므로,
      • NuPlanScenarioBuilder 내부에서 discover_log_dbs 함수를 통해
        • data_root 아래의 모든 DB 파일을 자동으로 검색
    • map_version:
      사용하려는 지도 버전을 지정합니다. (예: 'nuplan-maps-v1.0')
    • include_cameras, max_workers, verbose:
      • 시나리오 생성 시 추가로 고려할 옵션들로, 카메라 데이터를 포함할지, 동시 처리할 워커 수, 진행 상황 출력 여부 등을 설정
  • 시나리오 추출:
    • NuPlanScenarioBuilder는 내부적으로 nuPlan 로그(DB) 파일들을 읽어 각 로그에서 시나리오(예: 교차로 통과, 차선 변경 등)를 추출
    • 추출 시나리오는 나중에 시뮬레이션이나 학습에 사용되는 개별 시나리오 객체(AbstractScenario)로 구성됨
  • 맵 정보와 차량 정보 활용:
    • 지도 정보:
      map_root와 map_version을 이용해 NuPlan 지도 팩토리(AbstractMapFactory)를 생성하고, 이를 통해 각 시나리오에서 지도 정보를 불러옵니다.
    • 차량 정보:
      • VehicleParameters 객체를 사용하여 차량의 물리적 특성(너비, 전면/후면 길이, 휠베이스 등)을 제공
      • 이는 시나리오 내에서 차량 간 안전 거리 계산이나 충돌 여부 판단 등 물리적 제약 조건을 적용하는 데 필요합니다.
  • ScenarioMapping 활용:
    • ScenarioMapping 객체는 다양한 시나리오 유형에 대한 추출 지침을 담고 있습니다.
    • scenario_map:
      예시에서는 'accelerating_at_crosswalk', 'changing_lane', 'on_intersection' 등 수십 개의 시나리오 유형이 키로 제공되고, 각 유형에 대해 [15.0, -3.0]과 같이 시나리오의 지속시간, 시작 오프셋(예: 이벤트 발생 전후 시간) 등의 정보가 튜플로 정의되어 있습니다.
    • subsample_ratio_override:
      • 시나리오 추출 시 데이터 샘플링 비율을 지정하는 기본값(여기서는 0.5)을 제공
    • 이 정보들을 바탕으로 NuPlanScenarioBuilder는 각 시나리오 유형에 대해 어떤 시간 구간을 추출할지, 어떤 비율로 데이터를 샘플링할지 결정합니다.

VehicleParameters의 역할 및 기능

  • 차량의 물리적 특성 정의:
    • width, front_length, rear_length, wheel_base 등 차량의 치수를 설정합니다.
    • 또한, cog_position_from_rear_axle (후륜에서 중심까지의 거리)와 height 등도 포함되어 있습니다.
  • 계산 편의성:
    • 상속받은 BoxParameters의 half_width, half_length 프로퍼티를 통해 차량의 절반 크기를 쉽게 계산할 수 있습니다.
  • 차량 기반 연산:
    • 시나리오 추출이나 시뮬레이션에서 차량의 물리적 크기를 고려하여, 충돌 체크, 경로 계산 등 물리 모델링에 사용됩니다.
  • 예시:
    • 여기서는 Pacifica 차량에 대한 파라미터를 제공하며, 이 값들은 NuPlanScenarioBuilder가 시나리오에서 차량의 행동이나 위치 등을 정확하게 평가하는 데 사용됩니다.

ScenarioMapping의 역할 및 기능

  • 시나리오 유형 매핑:
    • 다양한 시나리오 유형(예: 'accelerating_at_crosswalk', 'changing_lane' 등)을 키(key)로 하고,
      • 해당 시나리오를 추출할 때 필요한 정보(지속시간, 시작 오프셋, 샘플링 비율)를 값(value)으로 갖는 딕셔너리를 전달받습니다.
  • 추출 정보 생성:
    • 각 시나리오 유형에 대해 ScenarioExtractionInfo 객체를 생성합니다.
    • 이 객체에는 시나리오 이름, 지속시간, 시작 오프셋, 그리고 데이터 샘플링 비율이 저장됩니다.
  • 기본값 적용:
    • 만약 특정 시나리오 유형에 대해 샘플링 비율이 지정되지 않으면, subsample_ratio_override 값을 기본으로 사용합니다.
  • 추출 지침 제공:
    • NuPlanScenarioBuilder는 이 ScenarioMapping을 사용하여 각 로그 파일에서 어떤 시나리오를 어떻게 추출할지 결정합니다.
    • 즉, 시나리오 추출 시 어떤 시간 구간을 선택할지, 얼마나 데이터를 샘플링할지 등의 기준을 제공합니다.




3. build_scenario_filter 함수

  • nuplan/planning/script/builders/scenario_filter_builder.py 에 있음
  • cfg.scenario_filter 가 들어감.

3.1. GPT의 설명

  • 이 함수는 Hydra로 로드된 설정(cfg)을 기반으로 "시나리오 필터(ScenarioFilter)" 객체를 생성하는 빌더 함수입니다. 구체적으로 수행하는 작업은 다음과 같습니다:
    요약하면, 이 함수는 cfg 설정에 따라 ScenarioFilter 객체를 생성하고, 토큰 형식 검증을 포함해 올바른 객체가 생성되었음을 확인한 후 반환하는 역할을 합니다.
  1. 시나리오 토큰 검증:

    • cfg에 scenario_tokens 항목이 존재할 경우, 이 리스트의 모든 토큰이 올바른 형식(예: 16자리 문자열)인지 is_valid_token 함수를 통해 확인합니다.
    • 만약 하나라도 올바르지 않은 토큰이 있다면, RuntimeError를 발생시켜 사용자가 토큰 형식을 재검토하도록 합니다.
    • 이 검증은 쉘에서 인자를 전달할 때 따옴표가 제거되어 숫자형(float)으로 해석되는 문제를 방지하기 위한 것입니다.
  2. 인스턴스 생성:

    • Hydra의 instantiate 함수를 사용해 cfg에 정의된 내용을 바탕으로 ScenarioFilter 객체를 생성합니다.
    • 이때, cfg는 시나리오 필터를 구성하기 위한 모든 파라미터(예: 필터 기준, 조건 등)를 포함하고 있어야 합니다.
  3. 타입 검증:

    • 생성된 객체가 올바른 타입(즉, AbstractScenarioFilter 또는 ScenarioFilter 인터페이스를 준수하는지)을 validate_type 함수를 통해 확인합니다.
  4. 로그 및 반환:

    • 생성된 시나리오 필터 객체를 출력(프린트)한 후, "Building ScenarioFilter...DONE!"이라는 로그를 남깁니다.
    • 최종적으로 이 객체를 반환하여, 이후 시나리오 필터링 단계에서 사용하게 됩니다.


3.2. 구체적인 설명

  • 이 ScenarioFilter는 데이터베이스에서 시나리오를 추출할 때
    • 적용할 여러 필터 조건들을 모아두는 "설정 컨테이너" 역할
  • 구체적으로 어떤 값들이 설정되고, 이들이 어떤 의미를 가지는지 살펴보면 다음과 같습니다.

결론

  • 위 YAML 설정을 통해 instantiate(cfg)를 호출하면, 다음과 같은 ScenarioFilter 인스턴스가 만들어집니다:

  • 시나리오 유형: 지정된 14개 유형(예: 'starting_left_turn', 'changing_lane' 등)만 포함

  • 시나리오 토큰, 로그, 지도 제한: 따로 지정하지 않으므로 모든 데이터를 사용

  • 각 유형당 최대 시나리오 수: 20개

  • 타임스탬프 기준: 시나리오 초기 타임스탬프 간 최대 15초의 차이를 기준으로 필터링

  • 나머지 속성: 불필요한 경우 제거(예: invalid goals는 제거됨)하며, 최종 시나리오 목록은 섞이지 않습니다.

  • 이 ScenarioFilter 객체는 nuPlan 시나리오 빌더가 데이터베이스에서 시나리오를 추출할 때, 위 조건들을 적용하여 적합한 시나리오만 선택하도록 돕습니다.


ScenarioFilter가 가지는 주요 속성 및 역할

  1. scenario_types

    • 설정값:
      [
          'starting_left_turn', 'starting_right_turn', 'starting_straight_traffic_light_intersection_traversal', 
          'stopping_with_lead', 'high_lateral_acceleration', 'high_magnitude_speed', 'low_magnitude_speed', 
          'traversing_pickup_dropoff', 'waiting_for_pedestrian_to_cross', 'behind_long_vehicle', 
          'stationary_in_traffic', 'near_multiple_vehicles', 'changing_lane', 'following_lane_with_lead'
      ]
    • 역할:
      이 리스트에 포함된 시나리오 유형만 추출하도록 필터링합니다. 즉, 예를 들어 좌회전, 우회전, 교차로 통과 등 특정 이벤트에 해당하는 시나리오만 선택하게 됩니다.
  2. scenario_tokens

    • 설정값:
      None
    • 역할:
      • 특정 시나리오(로그와 토큰의 쌍)를 직접 지정해서 필터링할 수 있으나, 여기서는 사용하지 않으므로 모든 시나리오에서 토큰 필터를 적용하지 않습니다.
  3. log_names

    • 설정값:
      None
    • 역할:
      특정 로그 파일 이름에 해당하는 시나리오만 선택하도록 할 수 있습니다. 여기서는 제한하지 않습니다.
  4. map_names

    • 설정값:
      None
    • 역할:
      특정 지도 이름에 해당하는 시나리오만 선택하도록 할 수 있습니다.
  5. num_scenarios_per_type

    • 설정값:
      20
    • 역할:
      각 시나리오 유형별로 최대 20개의 시나리오만 선택합니다. 예를 들어 "changing_lane" 시나리오가 50개 있더라도 20개만 추출합니다.
  6. limit_total_scenarios

    • 설정값:
      None
    • 역할:
      전체 시나리오 수를 제한하는 값입니다. 여기서는 전체 제한을 두지 않습니다.
  7. timestamp_threshold_s

    • 설정값:
      15
    • 역할:
      • 시나리오 초기 lidar 타임스탬프 간의 시간 간격이 15초를 넘으면 필터링하는 기준으로 사용됩니다.
      • 시간 간격이 너무 큰 경우(예: 데이터 누락 등)는 제외할 수 있습니다.
  8. ego_displacement_minimum_m

    • 설정값:
      None
    • 역할:
      • 시나리오 내에서 ego(자기 차량)가 움직인 최소 거리를 지정하여, 너무 짧은 (즉, 정지해 있거나 거의 움직이지 않은) 시나리오는 제외할 수 있는 옵션입니다.
      • 여기서는 적용하지 않습니다.
  9. ego_start_speed_threshold, ego_stop_speed_threshold, speed_noise_tolerance

    • 설정값:
      모두 None
    • 역할:
      ego의 시작 또는 정지 시 속도, 그리고 속도 변화의 잡음 허용치를 기준으로 시나리오를 필터링하는 옵션입니다. 여기서는 속도 관련 필터를 적용하지 않습니다.
  10. expand_scenarios

    • 설정값:
      False
    • 역할:
      여러 샘플을 포함한 시나리오를 단일 샘플로 확장하지 않습니다.
  11. remove_invalid_goals

    • 설정값:
      True
    • 역할:
      미션 목표(목표 상태)가 유효하지 않은 시나리오는 필터링(제외)합니다.
  12. shuffle

    • 설정값:
      False
    • 역할:
      최종적으로 추출된 시나리오 목록을 섞지 않습니다.

ScenarioFilter의 역할 및 기능

  • 시나리오 필터링:

    • 위 속성들에 정의된 조건들을 기반으로, nuPlan 데이터베이스에서 추출된 모든 시나리오 중에서
      • 원하는 유형, 특정 로그 또는 지도 조건, 수량 제한, 시간 간격 등의 조건을 만족하는 시나리오만 선택하도록 합니다.
  • 데이터 전처리:

    • 이 필터 객체는 시나리오를 추출하는 단계에서 기준 역할을 하며, 학습이나 시뮬레이션에 사용될 데이터셋을 깨끗하게 정제하고, 불필요하거나 품질이 낮은 데이터를 걸러내는 데 기여합니다.
  • 설정 검증:

    • __post_init__ 메서드를 통해, 설정된 값들이 올바른 범위(예: num_scenarios_per_type가 양수, limit_total_scenarios가 올바른 범위 내에 있음)인지 확인하여 잘못된 설정으로 인한 오류를 사전에 방지합니다.




profile
ad_official

0개의 댓글