Rust로 서버·클라이언트·공용 크레이트를 아우르는 앱을 만들기 위해 여러 UI/앱 프레임워크를 시도하다가 한계를 느끼고, 완전한 제어와 쉬운 사용성을 목표로 Ply를 만들게 된 과정과 철학, 1.0 기능, 문서 및 시작 방법을 소개합니다.
Rust로 게임을 만들고 싶었습니다. 서버, 클라이언트, 그리고 공유 게임 로직을 모두 담는 공용 크레이트를 갖춘 멀티플레이어 보드게임이요. 깔끔한 아키텍처. 하나의 언어. 완전한 제어.
그때 벽에 부딪혔습니다.
Bevy는 단순한 일도 수천 줄짜리 가상 데이터베이스 쿼리로 바꿔버리는 ECS 안으로 당신을 욱여넣습니다. UI 시스템은 매크로와 노드 기반이고, 여기저기에 impl Bundle과 ..default()가 흩어져 있습니다. Bevy의 아키텍처는 제가 서버를 위해 몇 주 동안 쌓아 올린 것과는 함께 동작할 수 없었습니다.
Iced는 유망해 보였지만 코드를 보자마자 생각이 바뀌었습니다. ..default()가 여기저기. 매 줄마다 .into(). 중첩 구조는 불분명하고, 모든 게 거꾸로 읽힙니다. 코드의 아래쪽에 가서야 최상위 요소가 나타나는 식이죠.
egui는 더 나았지만, 간격을 만들려고 직접 .add_space()를 호출하고 rect를 할당해야 합니다. 간단한 UI에는 괜찮습니다. 실제 앱에서는 금방 피곤해집니다.
Slint는 깔끔한 중첩 구조로 인상적이었지만, 별도의 마크업 언어입니다. Rust에 깔끔하게 통합하거나 기존 시스템과 연결할 수가 없습니다. parent.width 같은 참조나 in property <real> 선언은 Rust 코드베이스에 어울리지 않습니다.
Rust가 아닌 선택지도 살펴봤습니다. 저는 VR까지 포함해 Unity를 열심히 쓰는 개발자입니다. Unigine도 써 본 적이 있고, Unreal은 한 번 열었다가 혼란스럽고 짜증나서 닫았습니다. 전부 너무 둔하고, 제어권이 없습니다. 서버와 Godot을 공유 크레이트로 함께 굴린다고요? 불가능합니다. 또 예전에 Tauri로 전동 킥보드 대여 앱을 만들려다 끔찍한 경험을 했던 적도 있습니다.
결국 macroquad를 찾았습니다. 어디서나 실행된다고 했고, Love2D의 단순함에서 영감을 받은 듯 제가 원하던 것에 가까운 느낌이었습니다. 하지만 몇 시간 뒤, 분명해졌습니다. 이대로 계속하면 수년이 지나도 끝내지 못할 거라는 걸요. Macroquad는 앱 엔진이 아니라 렌더링 라이브러리입니다. 레이아웃 시스템도 없고, 텍스트 입력도 없고, UI 구조 자체가 없습니다.
그래서 그 위에 뭔가가 필요했습니다.
YouTube에서 C 레이아웃 라이브러리인 Clay를 들어본 적이 있었습니다. Rust 바인딩을 사용해서 macroquad와 함께 붙였습니다. 이름은 Clayquad라고 지었습니다.
처음엔 훌륭했습니다. 드디어 합리적인 속도로 게임을 만들 수 있었죠. 하지만 현실은 곧 따라왔습니다.
버그가 사방에서 터졌습니다. use-after-free. C 바인딩의 경쟁 조건. 텍스처 관리 부재. 빌림 검사기를 만족시키려고 매 프레임 이미지를 Box::leak하고 있었습니다. 문서는 빈약해서, 무언가를 알아내는 데 항상 시간이 오래 걸렸습니다.
그러다 확실한 한계에 부딪혔습니다. 셰이더를 원했지만 불가능했습니다. 회전을 원했습니다. 그래픽의 세 가지 기본 연산 중 하나인데도 Clay는 할 수 없었습니다. 스크롤은 수동으로 구현해야 했습니다. 텍스트 입력은 존재하지 않았습니다(상호작용 앱의 99%에 들어가는 그거요?). 크로스플랫폼 접근성 지원은 상상조차 할 수 없었습니다.
더 나은 무언가를 만들어야 했습니다.
저는 Clayquad 위에 계속 쌓아 올렸습니다. 렌더러, 텍스트 스타일링 시스템, 벡터 그래픽 지원. 2025년 말까지 기능은 계속 추가됐지만, 기반은 여전히 C였습니다. 그리고 기능을 추가할수록 문법은 점점 더 추해졌습니다. 여기저기 .end() 호출, 중첩된 요소마다 깊어지는 들여쓰기, 읽기 고통스러운 선언들.
저는 찾을 수 있는 모든 UI 프레임워크를 분석하기 시작했습니다. Iced, egui, Slint, Bevy, HTML/CSS, Qt/QML. 각각이 무엇을 잘했고 무엇을 잘못했는지 연구했습니다. 코드를 한 줄도 건드리기 전에 API가 어떻게 생겨야 하는지부터 알고 있었습니다.
2월에는 이 프로젝트에 집중했습니다. 레이아웃 엔진을 100% Rust로 포팅했고, 새벽 다섯 시까지 깨어서 동작하게 만들었습니다. 다음 날에는 제가 설계해 오던 새 API를 구현했습니다. 그 뒤로 셰이더, 접근성, CLI, 네트워킹… 그리고 이 웹사이트까지 왔습니다.
저는 빌더 패턴 + 클로저로 정착했습니다. 클로저는 .end() 문제를 해결합니다. 빌더 메서드는 모든 프로퍼티를 ..Default::default()로 지정하는 것보다 깔끔합니다. .shader() 호출을 체이닝할 수도 있고, .degrees()나 .radians()를 고를 수도 있으며, 모든 게 읽기 쉬운 상태로 유지됩니다.
use ply_engine::prelude::* 한 줄이면 필요한 게 전부 들어옵니다. 우리는 어디서나 Into<T>를 사용합니다. .background_color()가 Into<Color>를 받으면, 16진수 정수든, float 튜플이든, macroquad 색이든 다 받습니다. .image()가 Into<ImageSource>를 받으면, 파일 경로, 임베디드 바이트, 텍스처, 벡터 그래픽까지 다 받습니다. hex_to_macroquad_color!() 같은 래퍼는 필요 없습니다.
FloatingElementBuilder를 어떻게 구성하는지 찾아보느라 쓰지 않는 1초는 그대로 절약된 1초입니다.
Ply의 모든 결정 뒤에 있는 핵심 원칙: 완전한 제어권을 주면서도 더 쉽게 만들기.
왜 즉시 모드(immediate-mode)로, 매 프레임 UI를 다시 빌드하나요? 변이를 추적하는 것보다 실제로 더 빠르기 때문입니다. UI가 아무리 복잡해도 레이아웃은 전체 프레임 시간의 1%도 안 되는 일부만 차지하고, 대부분은 libnvidia나 GPU로 갑니다. 어차피 매 프레임 다시 그려야 합니다. Love2D가 이미 이게 잘 된다는 걸 증명했죠. 즉시 모드는 무엇을 언제 렌더링할지에 대한 완전한 제어를 제공합니다.
왜 하나의 prelude인가요? 어떤 개발자도 import 관리를 하고 싶지 않기 때문입니다. 하나의 import는 할 수 있는 일을 표준화하고 쓸모없는 보일러플레이트를 없앱니다.
왜 매니저(TEXTURE_MANAGER, MATERIAL_MANAGER, FONT_MANAGER, NET_MANAGER)인가요? 모든 것이 루프에서 실행되고, 반복 사이에 상태를 유지하는 좋은 방법은 많지 않기 때문입니다. Clayquad 시절에는 이미지에 대해 세 가지 선택지뿐이었습니다. 항상 로드해 두거나, 매 프레임 로드하거나, 아니면 직접 캐싱 시스템을 만들거나. Ply의 매니저는 이 모든 걸 백그라운드에서 처리합니다. 엔진에 이미지가 어디 있는지만 알려주면, 캐싱, 축출, 수명 관리를 처리합니다. 같은 패턴이 머티리얼, 폰트, 네트워크 요청에도 적용됩니다. 프레임 사이 메모리를 전반적으로 단순화해서, 당신은 그걸 생각하지 않아도 됩니다.
Ply 1.0에는 제가 시작할 때 존재하길 바랐던 것들이 전부 들어 있습니다.
어떤 엔진이든 문서의 품질만큼만 좋습니다. 엔진에 멋진 기능이 있어도 그걸 알아내는 데 두 시간이 걸린다면, 그 기능은 그저 방해물일 뿐입니다.
누구도 제가 무언가를 만들기 위해 읽어야 했던 만큼의 소스 코드를 읽을 필요가 없어야 합니다. 누구도 제가 했던 만큼의 pull request를 만들 필요가 없어야 합니다. 모든 것은 사용하기 쉬워야 합니다.
그래서 대화형 문서를 만들었습니다. 값을 조정하고 결과를 즉시 확인할 수 있는 라이브 코드 플레이그라운드입니다. 모든 개념에는 대화형 예제가 있습니다. 문서는 훈계로 가르치지 않고, 직접 해보게 하면서 가르칩니다.
cargo install plyx
plyx init
두 명령으로 Google Fonts의 폰트, feature flags, 프로젝트 구조가 갖춰진 앱을 얻을 수 있습니다.
예제를 확인해 보세요. 셰이더 플레이그라운드, 스네이크 게임, todo 앱이 있습니다. 홈 페이지에는 ply 문법 일부를 라이브로 시도해 볼 수 있는 인터프리터도 있습니다. 모든 것이 브라우저에서 실행됩니다.
대화형 문서도 살펴보세요. 브라우저에서 바로 코드를 만지작거릴 수 있는 대화형 예제를 보여줍니다. 소스는 GitHub에 있고, 라이선스는 Zero-Clause BSD입니다. 어떤 용도로든 사용하세요. 출처 표기도 필요 없습니다.
뭔가를 만들어 보세요.