28.3. Flight Task 프레임워크 객체 지향 설계 및 C++ 추상화

28.3. Flight Task 프레임워크 객체 지향 설계 및 C++ 추상화

과거 Ardupilot 초기 버전의 비행 모드 코드는 거대한 if-else 블록과 전역 변수(Global Variable)들이 뒤엉킨 모놀리식(Monolithic) 스파게티 코드의 전형을 보여주었다. 스태빌라이즈(Stabilize) 모드와 알트홀드(AltHold) 모드의 변수들이 파일 하나에서 서로 멱살을 잡고 간섭했으며, 새로운 비행 모드를 하나 추가하려면 수백 줄의 기존 코드를 뜯어고쳐야 하는 재앙에 가까운 유지보수성을 가졌다.

PX4 아키텍트들은 이런 C언어 카르텔식 스파게티 설계를 철저히 타파하기 위해, 현대 소프트웨어 공학의 꽃인 **객체 지향 프로그래밍(OOP: Object-Oriented Programming)**의 다형성(Polymorphism)과 캡슐화(Encapsulation) 원칙을 비행 제어 루탄의 정중앙에 플러그인 형태로 이식했다. 그 거대한 객체 지향 설계의 결정체가 바로 FlightTask 프레임워크다.

1. 다형성(Polymorphism)을 통한 ‘모듈교체형’ 비행 모드

FMM(Flight Mode Manager) 데몬의 핵심 임무는 매 루프마다 현재 선택된 비행 모드의 궤적 계산 공식을 실행하는 것이다. 놀랍게도 FMM의 Run() 루프 안에는 “지금이 수동 모드인가? 오토 모드인가?“를 묻는 촌스러운 switch-case 제어문이 단 한 줄도 선언되어 있지 않다.

  • 다형적 기저 포인터(Polymorphic Base Pointer):
    FMM은 내부적으로 오직 FlightTask* _current_task 라는, 추상화된 최상위 부모 클래스(FlightTask) 포인터 단 하나만을 쥐고 있다. 이 빈 껍데기 포인터는 런타임에 팩토리 패턴을 통해 생성된 다양한 자식 클래스(예: FlightTaskManual, FlightTaskAutoMission 등)의 주소값을 묵묵히 넘겨받아 치환된다.
  • Virtual 테이블을 통한 동적 바인딩(Dynamic Binding):
    FMM은 매 루프마다 생각 없이 그저 _current_task->update() 라는 가상 함수(Virtual Function) 단 한 줄만을 호출한다. C++ 컴파일러가 만들어놓은 vtable(가상 함수 테이블) 레이아웃 덕분에, CPU는 런타임에 현재 포인터가 가리키는 ‘진짜’ 자식 객체의 메모리 번지를 동적으로 추적하여, 수동 모드면 수동 모드 계산식을, 이륙 모드면 이륙 모드 계산식을 자동으로 실행시킨다.
    이 다형성 폴리모피즘(Polymorphism) 덕분에 PX4 소스 코드는 수십 개의 비행 모드를 스위칭하면서도 if-else의 지옥에서 완벽히 해방될 수 있었다.

2. 엄격한 캡슐화(Encapsulation)와 데이터 격리 공간 (Sandbox)

비행 모드가 꼬이는 가장 큰 이유는 변수들의 전역적 생존(Global State) 때문이다. 미션 모드에서 기체를 가속시키기 위해 썼던 target_speed 변수를, 착륙 모드로 넘어가서 미처 초기화하지 않고 그대로 참조했다가 드론이 땅에 곤두박질치는 에러가 과거 레거시 시스템에서는 빈번했다.

FlightTask 프레임워크는 각 비행 모드들을 완벽하게 밀폐된 **C++ 클래스 객체 단위의 샌드박스(Sandbox)**로 취급한다.

  • 독립된 Heap/Stack 라이프사이클:
    조종사가 ’미션 모드’에서 ’수동 모드’로 딸깍 스위치를 바꾸는 순간, FMM 부모는 기존 FlightTaskAutoMission 객체를 메모리에서 산 채로 delete(소멸)시켜버린다. 그 안에 들어있던 온갖 쓰레기 계산 변수, 적분기 I-term 에러값, 플래그들은 클래스 소멸자(Destructor)와 함께 램(RAM)에서 흔적도 없이 완전히 날아가버린다.
  • 클린 룸(Clean Room) 초기화:
    이어서 1마이크로초 만에 새로운 FlightTaskManual 객체가 new 연산자로 힙(Heap) 메모리에 할당된다. 이 새로운 자식 객체는 과거의 더러운 상태 값들에 전혀 오염되지 않은, 변수들이 0.0f로 새하얗게 초기화된 클린 룸 상태에서 태어난다.
    이 엄혹한 생성/소멸(Instantiation/Destruction) 캡슐화 장벽 덕분에, 모드 전환 시 이전 모드의 수학적 찌꺼기가 새 모드로 전염되는 아키텍처적 전염병(State Leakage)이 구조적으로 불가능해졌다.

3. 부모 클래스(Base Class) 주도의 공통 리소스 은닉 (Abstraction)

개별 비행 모드 개발자들(예를 들어 새로운 ‘자동 추적’ 모드를 만드는 대학원생)이 가장 귀찮아하는 작업은 센서 데이터를 uORB 바닥부터 긁어오는 반복 작업이다.

FlightTask 아키텍처는 이를 해결하기 위해 최상위 부모 추상 클래스에 강력한 추상화 은닉(Abstraction Hiding) 권력을 몰아주었다.

  • Dependency Injection의 수혜자:
    자식 객체(커스텀 비행 모드)는 센서나 OS 시간을 어디서 가져와야 하는지 전혀 알 필요가 없다. 윗단 FMM 데몬이 이미 update() 가상 함수를 호출하기 직전에, 통합된 최신 센서 데이터 스냅샷 블록 하나를 자식 객체의 내부 구조체 포인터로 쓰윽 밀어 넣어준다 (주입).
  • 상속(Inheritance)을 통한 인터페이스 강제:
    따라서 FlightTask를 상속받는 모든 자식 비행 모드 코드들은, 부모가 제공하는 순수 가상 함수 인터페이스 구조 템플릿(코드 뼈대) 모양을 그대로 지키면서, 오로지 이센서가 주어졌을 때 모터를 얼마나 돌릴지 **순수한 수학 공식(궤적 연산)**에만 집중하여 코딩하면 된다.
    이는 운영체제의 시스템 리소스 관리와 애플리케이션의 궤적 수학을 완벽하게 분리해 낸 우아한 미들웨어 추상화(Middleware Abstraction) 계층의 훌륭한 교과서적 사례다.