충돌
고친 사람 github-actions[bot]
충돌은 둘이 하나뿐인 것을 동시에 차지하려 할 때 벌어지는 일을 가리킵니다. 버전 관리에서는 두 사람이 같은 줄을 서로 다르게 고쳐서 합치기가 멈춘 상태입니다. 게임에서는 움직이던 두 물체가 서로 닿거나 겹친 순간입니다. 분야마다 가리키는 것이 달라서 어느 분야의 이야기인지부터 봐야 합니다.
쉽고 빠른 이해
무슨 일인가 — 둘이 하나뿐인 것을 두고 부딪힌 상태입니다. 나와 동료가 같은 줄을 다르게 고친 뒤 합치면 Git 은 멈추고 어느 쪽을 남길지 묻습니다. 게임에서 공이 벽에 닿으면 그 순간이 충돌입니다. 게임은 공을 튕겨 냅니다.
왜 알아채야 하나 — 버전 관리 도구가 아무 쪽이나 골랐다면 다른 사람의 수정이 말없이 지워졌을 겁니다. 게임이 충돌을 놓치면 캐릭터가 벽을 뚫고 지나갑니다. 그래서 두 분야 모두 충돌을 먼저 찾아내고 따로 처리합니다.
어떻게 도나
- 기계가 부딪힘을 찾아냅니다
- 버전 관리는 멈추고 사람에게 고르게 합니다. 게임은 정해 둔 규칙대로 물체를 밀어내거나 튕겨 냅니다
- 처리가 끝나면 하던 일을 이어 갑니다
언제 신경 쓰나 — 충돌은 켜고 끄는 기능이 아니라 저절로 일어나는 일입니다. 버전 관리에서는 자주 작게 합칠수록 덜 납니다. 게임에서는 부딪힐 일이 있는 물체에만 충돌 검사를 붙입니다.
대가 — 버전 관리의 충돌은 사람이 읽고 골라야 해서 시간이 듭니다. 게임의 충돌은 화면을 새로 그릴 때마다 물체 쌍을 검사해야 해서 계산이 듭니다.
상세
충돌이 가리키는 뜻은 분야마다 다릅니다. 이 절은 그 뜻들을 먼저 표로 가릅니다. 그다음 이 낱말을 가장 자주 만나는 버전 관리와 게임의 충돌을 차례로 따라갑니다.
충돌이 가리키는 뜻들
| 분야 | 무엇과 무엇이 부딪히나 | 영어 이름 |
|---|---|---|
| 버전 관리 | 같은 줄을 서로 다르게 고친 두 변경 | conflict |
| 복제 데이터베이스 | 같은 값을 서로 다르게 고친 두 사본 | conflict |
| 게임 | 공간에서 서로 닿거나 겹친 두 물체 | collision |
| 해시 | 같은 해시 값을 받은 서로 다른 두 입력 | collision |
| 네트워크 | 한 선에 동시에 실린 두 신호 | collision |
| 프로그램의 이름 | 같은 이름을 쓰는 서로 다른 두 대상 | name collision · name clash |
행마다 부딪히는 것은 다르지만 뼈대는 같습니다. 줄 하나, 공간 한 곳, 값 하나, 선 하나, 이름 하나처럼 하나뿐인 것을 둘이 동시에 요구합니다. 한국어는 이 모두를 충돌 한 낱말로 부릅니다.
영어는 둘로 나눕니다. conflict 는 두 내용이 서로 맞서서 어느 쪽이 맞는지 정해야 하는 경우입니다. collision 은 두 대상이 같은 곳에서 부딪히는 경우입니다. 영어 문서라면 이 낱말만 보고도 어느 갈래인지 짐작할 수 있습니다.
버전 관리의 충돌
버전 관리 도구는 코드를 고친 내용을 한 묶음씩 이력에 새깁니다. 이 한 묶음을 커밋이라고 부릅니다. 커밋이 쌓이면 코드가 언제 어떻게 바뀌었는지 되짚을 수 있습니다.
여러 사람이 같은 코드를 고칠 때는 이력을 갈래로 나눕니다. 각자 자기 갈래에서 커밋을 쌓고 나중에 하나로 합칩니다. 이 갈래를 브랜치라고 부릅니다. 갈래를 하나로 합치는 일은 병합입니다.
병합할 때 도구는 두 갈래가 갈라지기 직전의 상태를 기준으로 삼습니다. 이 기준을 공통 조상이라고 부릅니다. 도구는 기준과 견줘 양쪽이 각각 무엇을 바꿨는지 줄 단위로 봅니다.
대부분의 줄은 저절로 정해집니다. 아무도 안 고친 줄은 그대로 둡니다. 한쪽만 고친 줄은 고친 쪽을 씁니다. 양쪽이 똑같이 고친 줄도 그 내용을 씁니다. 남는 것은 양쪽이 같은 줄을 서로 다르게 고친 경우 하나뿐입니다.
flowchart TD
A["공통 조상과 견줘 한 줄씩 본다"] --> B{"누가 고쳤나"}
B -->|아무도| C["원래 줄을 둔다"]
B -->|한쪽만| D["고친 쪽을 쓴다"]
B -->|양쪽 다| E{"고친 내용이 같나"}
E -->|같다| F["그 내용을 쓴다"]
E -->|다르다| G["충돌 · 멈추고 사람에게 넘긴다"]
이 마지막 경우에 도구는 어느 쪽이 맞는지 모릅니다. 아무 쪽이나 고르면 다른 사람의 수정이 말없이 지워집니다. 그래서 도구는 병합을 멈추고 사람에게 넘깁니다. 이 멈춘 상태가 버전 관리에서 말하는 충돌입니다.
충돌이 나면 도구는 문제의 줄을 파일 안에 두 벌 다 적어 둡니다. Git 은 기호 줄로 두 벌을 가릅니다. 아래는 설정값 하나를 나는 30 으로, 동료는 60 으로 바꾼 경우입니다.
<<<<<<< HEAD
timeout = 30
=======
timeout = 60
>>>>>>> feature
<<<<<<< 줄부터 ======= 줄까지가 지금 내가 있는 브랜치의 내용입니다. Git 은 지금 있는 브랜치를 HEAD 라는 이름으로 가리킵니다. ======= 줄부터 >>>>>>> 줄까지는 합쳐 들어오는 feature 브랜치의 내용입니다.
사람은 둘 중 하나를 고르거나 둘을 섞어 새로 씁니다. 그다음 기호 줄을 지우고 다 풀었다고 도구에 알립니다. 그러면 멈췄던 병합이 이어집니다. 이렇게 사람이 최종본을 정하는 일을 충돌 해소라고 부릅니다.
변경을 옮겨 얹는 명령의 충돌
병합만 충돌로 멈추는 것이 아닙니다. 커밋을 다른 곳에 옮겨 얹는 명령도 얹을 때마다 병합과 같은 비교를 합니다. 그래서 같은 이유로 멈출 수 있습니다.
리베이스는 내 커밋들을 다른 브랜치 끝에 하나씩 다시 얹습니다. 하나씩 얹으므로 커밋마다 충돌로 멈출 수 있습니다. 내 커밋 여럿이 같은 줄을 고쳤다면, 그 커밋을 얹을 때마다 그 줄에서 다시 멈출 수 있습니다.
체리픽은 다른 브랜치의 커밋 하나만 골라 지금 브랜치에 얹습니다. 브랜치를 통째로 합치지 않고 필요한 수정 하나만 가져올 때 씁니다.
되돌리기는 지난 커밋이 만든 변경을 반대로 얹어 없던 일로 만듭니다. 지난 커밋은 이력에 그대로 남습니다. 반대 변경이 새 커밋으로 하나 더 쌓입니다.
두 명령 모두 얹으려는 줄을 그사이 다른 커밋이 또 고쳤으면 충돌로 멈춥니다. 멈춘 뒤 푸는 방법은 병합의 충돌과 같습니다.
줄이 안 겹치는 어긋남
도구가 보는 것은 같은 줄을 건드렸는지뿐입니다. 코드의 뜻이 서로 어긋나는지는 보지 않습니다. 그래서 충돌 없이 끝난 병합이 깨진 코드를 낳기도 합니다.
한 사람은 함수 이름을 load 에서 fetch 로 바꿨습니다. 다른 사람은 다른 파일에 load 를 부르는 줄을 새로 더했습니다. 두 변경은 서로 다른 줄이라 병합은 멈추지 않고 끝납니다. 그런데 합친 코드에는 이제 없는 load 를 부르는 줄이 남습니다.
이런 어긋남은 줄 단위 충돌 검사로는 안 잡힙니다. 합친 뒤의 빌드나 테스트가 잡아야 합니다.
충돌이 잦아지는 조건
두 갈래가 오래 떨어져 있을수록 같은 줄을 서로 고칠 일이 늘어납니다. 그래서 오래 살아 있는 브랜치일수록 충돌이 잦고 한 번에 크게 납니다.
거꾸로 브랜치를 짧게 유지하고 자주 합치면 충돌이 작고 드물어집니다. 트렁크 기반 개발과 지속적 통합이 이 방향으로 짜인 작업 방식입니다.
게임의 충돌
화면을 한 번 새로 그리는 단위를 프레임이라고 합니다. 게임 세계의 물체는 프레임마다 조금씩 움직입니다. 움직인 뒤 두 물체가 서로 닿거나 겹쳐 있으면 두 물체는 충돌한 것입니다.
충돌을 놓치면 물체들이 서로를 그냥 통과합니다. 캐릭터가 벽을 뚫고 지나갑니다. 총알은 적을 맞히지 못하고 지나칩니다. 게임은 이런 일을 막으려고 프레임마다 충돌을 찾아 처리합니다.
이 일은 두 단계로 나뉩니다. 두 물체가 겹쳤는지 알아내는 계산을 충돌 감지라고 합니다. 게임에서는 보통 물리 엔진이 이 계산을 맡습니다.
겹친 것을 알아낸 뒤 무엇을 할지는 충돌 응답이라고 합니다. 공이면 튕겨 냅니다. 캐릭터가 벽에 박혔으면 벽 밖으로 밀어냅니다. 총알이 적에 닿으면 튕기지 않고 피해만 줍니다.
충돌 모양을 단순하게 두는 까닭
물체의 실제 모양은 복잡해서 그대로 겹침을 재면 계산이 많이 듭니다. 그래서 물체에 상자·구·캡슐 같은 단순한 도형을 씌웁니다. 이 도형을 콜라이더라고 부릅니다.
게임은 겹침을 콜라이더끼리만 봅니다. 부딪힐 일이 없는 장식 물체에는 콜라이더를 붙이지 않아 계산을 아낍니다.
구 둘이 겹쳤는지는 계산 한 줄로 가립니다. 두 중심 사이 거리가 두 반지름의 합보다 작으면 겹친 것입니다. 반지름이 1 인 공 둘을 놓고 거리만 바꿔 봅니다.
r = 1 # 두 공의 반지름
d = 1.5 # 중심 사이 거리
d < r + r # True, 충돌
d = 3
d < r + r # False, 안 닿음
첫 경우는 거리 1.5 가 반지름의 합 2 보다 작아서 두 공이 겹칩니다. 둘째 경우는 거리 3 이 2 보다 커서 떨어져 있습니다. 상자나 캡슐도 이처럼 짧은 계산으로 가립니다.
검사할 쌍을 줄이는 두 단계
물체가 많으면 쌍의 수가 문제입니다. 물체 n 개에서 둘씩 짝을 지으면 n(n−1)/2 쌍이 나옵니다. 물체가 100 개면 4,950 쌍이라 매 프레임 전부 정밀하게 재기는 버겁습니다.
검사는 두 번에 나눕니다. 먼저 공간을 칸으로 나누거나 물체를 큰 상자로 감싸서, 멀리 떨어진 쌍을 싸게 걸러 냅니다. 그다음 남은 가까운 쌍만 콜라이더 모양대로 정밀하게 잽니다. 앞 단계를 broad phase, 뒤 단계를 narrow phase 라고 부릅니다.
flowchart TD
A["물체를 한 프레임만큼 움직인다"] --> B["멀리 떨어진 쌍을 걸러 낸다"]
B --> C["남은 쌍을 콜라이더 모양대로 잰다"]
C --> D["겹친 쌍에 충돌 응답을 적용한다"]
D --> E["화면을 그린다"]
E -->|다음 프레임| A
이 순서가 프레임마다 되풀이됩니다. 걸러 내는 단계가 싸야 정밀한 단계에 계산을 몰아줄 수 있습니다.
프레임 사이로 빠져나가는 충돌
게임은 프레임과 프레임 사이를 보지 않습니다. 아주 빨리 나는 총알은 한 프레임 만에 얇은 벽 너머로 넘어가서, 벽과 겹친 순간이 한 번도 잡히지 않습니다. 이렇게 충돌을 놓치고 빠져나가는 일을 터널링이라고 부릅니다.
빨리 움직이는 물체에는 움직인 경로 전체를 훑어 벽과 스쳤는지 보는 방법을 씁니다. 이를 연속 충돌 감지라고 합니다. 계산이 더 들기 때문에 빨리 움직이는 물체에만 켭니다.
그 밖의 충돌
버전 관리와 게임 말고도 충돌이라는 낱말을 쓰는 분야가 넷 더 있습니다. 각각 따로 다루는 항목이 있어서 여기서는 한 문단씩만 짚습니다.
해시 함수는 아무 길이의 입력을 정해진 길이의 값으로 바꿉니다. 입력의 가짓수가 값의 가짓수보다 훨씬 많아서, 서로 다른 입력이 같은 값을 받는 일은 피할 수 없습니다. 이것이 해시 충돌입니다.
해시테이블은 해시 값을 칸(버킷) 번호로 삼아 데이터를 넣어 두는 자료구조입니다. 두 입력이 같은 칸을 받아도 해시테이블은 충돌을 없애려 하지 않습니다. 예를 들어 그 칸에 목록을 달아 두 입력을 다 넣어 둡니다.
여러 장비가 선 하나를 나눠 쓰던 옛 이더넷에서는 두 장비가 동시에 보내면 신호가 겹쳐 둘 다 깨졌습니다. 이 겹침도 충돌입니다. 신호가 서로 겹칠 수 있는, 선 하나를 나눠 쓰는 장비들의 묶음을 충돌 도메인이라고 합니다.
스위치는 이 겹침을 없앤 장비입니다. 장비마다 선을 따로 주어 두 신호가 한 선에서 만나지 않게 합니다.
같은 데이터를 여러 서버에 사본으로 두는 일을 복제라고 합니다. 두 사본이 거의 같은 때에 같은 값을 서로 다르게 고치면, 어느 값을 남길지 정해야 합니다. 이것이 쓰기 충돌입니다. 버전 관리의 충돌처럼 conflict 쪽에 듭니다.
프로그램 안에서 서로 다른 두 대상이 같은 이름을 쓰면, 그 이름을 불렀을 때 어느 쪽인지 정할 수 없습니다. 두 라이브러리가 똑같이 Logger 라는 클래스를 내놓는 경우가 그렇습니다. 네임스페이스는 이름 앞에 소속을 붙여 두 Logger 를 가려 줍니다.
어느 뜻인지 가르는 단서
글에서 충돌을 만나면 함께 나온 낱말을 보면 됩니다. 아래 표는 자주 같이 나오는 낱말과 그때의 뜻입니다.
| 함께 나오는 말 | 뜻 |
|---|---|
| 병합 · 브랜치 · 커밋 · 리베이스 | 버전 관리의 충돌 |
| 사본 · 복제 · 동기화 | 복제의 쓰기 충돌 |
| 콜라이더 · 물리 · 프레임 · 튕김 | 게임의 충돌 |
| 해시 값 · 버킷 · 해시테이블 | 해시 충돌 |
| 이더넷 · 허브 · 신호 · 재전송 | 네트워크의 충돌 |
| 네임스페이스 · import · 같은 이름 | 이름 충돌 |
관련 항목
버전 관리에서 충돌로 멈출 수 있는 명령
병합 · 리베이스 · 체리픽 · 되돌리기 · 풀 · 스태시
버전 관리 충돌을 푸는 데 쓰는 개념
충돌 해소 · 3방향 병합 · 공통 조상 · 충돌 마커 · 병합 커밋 · HEAD · 병합 도구
버전 관리 충돌을 줄이는 작업 방식
트렁크 기반 개발 · 지속적 통합 · 풀 리퀘스트 · 기능 플래그
게임에서 충돌을 찾고 처리하는 기술과 그 한계
콜라이더 · 충돌 감지 · 충돌 응답 · 물리 엔진 · 강체 물리 · 바운딩 박스 · AABB · 공간 분할 · 연속 충돌 감지 · 터널링 · 히트박스
게임 충돌 검사가 도는 시간 단위
게임 루프 · 프레임 · 고정 타임스텝 · 물리 스텝 · 델타 타임
해시 값이 겹치는 충돌을 다루는 개념
해시 충돌 · 해시 함수 · 해시테이블 · 충돌 저항성 · 생일 문제 · 비둘기집 원리
한 선을 나눠 쓰는 네트워크의 충돌을 다루는 장비·규칙
충돌 도메인 · CSMA/CD · 이더넷 · 허브 · 스위치 · 전이중
복제 사본끼리 값이 갈리는 충돌을 다루는 기법
쓰기 충돌 · 복제 · 버전 벡터 · 벡터 시계 · Last Write Wins · CRDT
같은 이름이 겹치는 충돌을 가르는 장치
이름 충돌 · 네임스페이스 · 식별자 · 스코프 · 패키지