[P-Reinforce] 2026-04-20: Processed 5 New Knowledge Gems (Tetris Engineering Lessons)
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
---
|
||||
# 💡 Lesson Learned: Web Worker를 이용한 고성능 아키텍처 설계 (Performance)
|
||||
|
||||
## 🎯 문제 상황 (The Problem)
|
||||
테트리스 게임과 같이 **매우 높은 빈도(High Frequency)**로 상태 변화가 발생하는 실시간 애플리케이션을 React의 메인 스레드에서 처리할 경우, UI 업데이트와 물리 계산이 충돌하여 **프레임 드롭(Jank)** 현상이나 성능 저하가 발생했습니다.
|
||||
|
||||
## 🔬 근본 원인 (Root Cause)
|
||||
게임 엔진 로직은 CPU를 매우 많이 사용합니다. 이 무거운 계산을 메인 스레드에서 수행하면, 브라우저의 UI 업데이트 루프(`requestAnimationFrame`)와 충돌하여 사용자에게 부드럽지 않은 경험(Poor UX)을 제공하게 됩니다.
|
||||
|
||||
## ✅ 해결책 (The Solution)
|
||||
**Web Worker**를 사용하여 게임 엔진 로직 전체를 **메인 스레드에서 완전히 분리(Isolate)** 했습니다.
|
||||
* **원리:** Web Worker는 별도의 백그라운드 스레드에서 동작하므로, 아무리 복잡한 계산을 해도 메인 스레드의 UI 렌더링에는 영향을 주지 않습니다.
|
||||
|
||||
## 💡 교훈 (Lesson Learned)
|
||||
> **"성능 병목 현상은 종종 '스레딩(Threading)'의 문제이다."**
|
||||
> 실시간으로 높은 연산량이 요구되는 모든 시스템은, 반드시 Web Worker 또는 별도의 백그라운드 프로세스로 로직을 분리하여 처리해야 합니다.
|
||||
|
||||
## 🔗 관련 키워드
|
||||
`Web Worker`, `Concurrency`, `High-Frequency Updates`, `Performance Optimization`
|
||||
---
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
# 💡 Lesson Learned: 상태 관리의 단일 진실 공급원 원칙 (Data Consistency)
|
||||
|
||||
## 🎯 문제 상황 (The Problem)
|
||||
테트리스 게임은 '현재 보드 상태'와 '움직이는 블록 위치'라는 두 가지 핵심 데이터를 가지고 있습니다. 이 데이터들이 여러 곳에서 독립적으로 업데이트될 위험이 있었습니다. 만약 A 부분에서 값을 바꾸고, B 부분에서 같은 값을 다르게 계산한다면 **데이터 불일치(Inconsistency)**가 발생합니다.
|
||||
|
||||
## 🔬 근본 원인 (Root Cause)
|
||||
시스템의 핵심 상태가 분산되어 관리되고 있었기 때문입니다. 여러 컴포넌트와 로직이 각자 '진실'이라고 믿는 데이터를 가지고 충돌할 가능성이 높았습니다.
|
||||
|
||||
## ✅ 해결책 (The Solution)
|
||||
**Redux/Zustand 패턴을 차용하여 모든 게임의 핵심 상태(State)**를 `src/TetrisGame.jsx` 컴포넌트가 관리하는 **단일 지점(Single Source of Truth)**으로 만들었습니다. 모든 데이터 변경은 이 중앙 저장소를 통해 이루어지게 했습니다.
|
||||
|
||||
## 💡 교훈 (Lesson Learned)
|
||||
> **"상태는 오직 한 곳에서만 정의하고, 모든 로직은 그 상태를 읽고 쓰는 방식으로 동작해야 한다."**
|
||||
> 복잡한 시스템을 설계할 때, 핵심 데이터의 흐름(Data Flow)과 책임 범위(Responsibility)를 명확히 분리하는 것이 가장 중요합니다.
|
||||
|
||||
## 🔗 관련 키워드
|
||||
`Single Source of Truth`, `Redux Pattern`, `State Management`, `Predictable State`
|
||||
---
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
# 💡 Lesson Learned: 시스템 아키텍처의 중요성 (The Need for Abstraction)
|
||||
|
||||
## 🎯 문제 상황 (The Problem)
|
||||
이번 프로젝트를 진행하면서, 코드를 짜는 것이 아니라 '어떤 구조로 짤지'가 가장 어려웠습니다. 이는 단순히 기술적인 문제가 아닌 **설계 패턴(Design Pattern)**과 관련된 문제입니다.
|
||||
|
||||
## 🔬 근본 원인 (Root Cause)
|
||||
모든 로직을 한 파일에 때려 넣으려는 유혹에 빠지는 것, 즉 '스파게티 코드'를 만들 위험이 가장 큰 문제였습니다. 모든 것을 한곳에서 처리하려 했기 때문에 유지보수성과 확장성이 0에 수렴했습니다.
|
||||
|
||||
## ✅ 해결책 (The Solution)
|
||||
**아키텍처적 분리 원칙(Separation of Concerns, SoC)**을 적용하여 코드를 다음과 같이 역할별로 나눴습니다:
|
||||
1. **게임 규칙:** `gameWorker.js` (논리 엔진)
|
||||
2. **상태 관리:** `TetrisGame.jsx` (데이터의 출입구)
|
||||
3. **렌더링:** React 컴포넌트 (화면에 보여주는 역할만 수행)
|
||||
|
||||
## 💡 교훈 (Lesson Learned)
|
||||
> **"시스템을 구성할 때는 '책임 분리(Separation of Concerns)'를 최우선 원칙으로 삼아야 한다."**
|
||||
> 기능이 복잡해질수록, 코드는 반드시 경계가 명확한 모듈들로 분리되어야 합니다.
|
||||
|
||||
## 🔗 관련 키워드
|
||||
`Separation of Concerns`, `Modular Design`, `Microservices Pattern`
|
||||
---
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
# 💡 Lesson Learned: 개발 환경 및 실행 프로세스 관리 (DevOps & DevOps)
|
||||
|
||||
## 🎯 문제 상황 (The Problem)
|
||||
이번 프로젝트는 단순히 코드를 짜고 끝나는 것이 아니라, **'어떻게 이 코드를 구동시킬 수 있는가?'**라는 물리적 절차의 중요성을 깨달았습니다. (오류 코드: `npm audit`, `index.html` 누락, 권한 오류 등)
|
||||
|
||||
## 🔬 근본 원인 (Root Cause)
|
||||
개발자는 종종 **'논리적 완성도(Logical Completion)'에만 집중**하고, 프로젝트를 실행하는 데 필요한 **물리적인 설정 파일(Configuration)**과 **운영체제 레벨의 환경 변수/권한** 관리에 소홀해지기 쉽습니다.
|
||||
|
||||
## ✅ 해결책 (The Solution)
|
||||
프로젝트 시작 시점에 다음 절차를 반드시 거쳐야 함을 확립했습니다:
|
||||
1. `npm install`: 필요한 모든 패키지를 설치한다.
|
||||
2. 환경 설정 확인: `public/index.html` 등 필수 진입점이 존재하는지 확인한다.
|
||||
3. 권한 확보: 운영체제 레벨에서 스크립트 실행 권한(Execution Policy)을 확보한다.
|
||||
|
||||
## 💡 교훈 (Lesson Learned)
|
||||
> **"코딩 능력만큼이나 중요한 것은 '운영 환경에 대한 이해'와 '체계적인 개발 프로세스 확립'이다."**
|
||||
> 프로젝트 관리자는 항상 이 세 가지 단계를 점검해야 합니다.
|
||||
|
||||
## 🔗 관련 키워드
|
||||
`DevOps`, `CI/CD Pipeline`, `Execution Policy`, `Build Environment`
|
||||
---
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
# 💡 Lesson Learned: 시스템 시뮬레이션의 핵심 원리 (Simulation Design)
|
||||
|
||||
## 🎯 문제 상황 (The Problem)
|
||||
테트리스는 단순한 게임이 아니라, **물리 법칙(Physics)**과 **규칙 기반의 상태 변화**가 작동하는 작은 시뮬레이터였습니다. 이 경험을 통해 '시뮬레이션을 어떻게 설계해야 하는지'에 대한 깊은 이해를 얻었습니다.
|
||||
|
||||
## 🔬 근본 원인 (Root Cause)
|
||||
단순히 UI로 그리는 것에만 집중하면, 시스템이 **규칙(Ruleset)**과 **물리 법칙(Physics Law)**을 따르는 '가상 세계'의 느낌을 놓치기 쉽습니다.
|
||||
|
||||
## ✅ 해결책 (The Solution)
|
||||
게임 로직을 `gameWorker.js`에 완전히 분리하여, 모든 변화를 수학적 함수(`checkCollision`, `movePiece`)로 처리하고 그 결과를 상태(State)에 반영했습니다. 이는 곧 **"규칙이 물리 법칙처럼 작동하는 시스템"** 설계의 성공적인 예시입니다.
|
||||
|
||||
## 💡 교훈 (Lesson Learned)
|
||||
> **"모든 시뮬레이션은 '물리적 규칙'을 수학적으로 정의하고, 그 규칙을 절대 우회할 수 없도록 강제해야 한다."**
|
||||
> 이를 통해 우리는 단순한 게임을 넘어, 자율주행이나 물리 엔진에 적용 가능한 고수준의 시스템 모델링 능력을 갖추게 되었습니다.
|
||||
|
||||
## 🔗 관련 키워드
|
||||
`Simulation Design`, `Physics Engine`, `Ruleset Enforcement`, `Systemic Modeling`
|
||||
---
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"file-explorer": true,
|
||||
"global-search": true,
|
||||
"switcher": true,
|
||||
"graph": true,
|
||||
"backlink": true,
|
||||
"canvas": true,
|
||||
"outgoing-link": true,
|
||||
"tag-pane": true,
|
||||
"footnotes": false,
|
||||
"properties": true,
|
||||
"page-preview": true,
|
||||
"daily-notes": true,
|
||||
"templates": true,
|
||||
"note-composer": true,
|
||||
"command-palette": true,
|
||||
"slash-command": false,
|
||||
"editor-status": true,
|
||||
"bookmarks": true,
|
||||
"markdown-importer": false,
|
||||
"zk-prefixer": false,
|
||||
"random-note": false,
|
||||
"outline": true,
|
||||
"word-count": true,
|
||||
"slides": false,
|
||||
"audio-recorder": false,
|
||||
"workspaces": false,
|
||||
"file-recovery": true,
|
||||
"publish": false,
|
||||
"sync": true,
|
||||
"bases": true,
|
||||
"webviewer": false
|
||||
}
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"collapse-filter": true,
|
||||
"search": "",
|
||||
"showTags": false,
|
||||
"showAttachments": false,
|
||||
"hideUnresolved": false,
|
||||
"showOrphans": true,
|
||||
"collapse-color-groups": true,
|
||||
"colorGroups": [],
|
||||
"collapse-display": true,
|
||||
"showArrow": false,
|
||||
"textFadeMultiplier": 0,
|
||||
"nodeSizeMultiplier": 1,
|
||||
"lineSizeMultiplier": 1,
|
||||
"collapse-forces": true,
|
||||
"centerStrength": 0.518713248970312,
|
||||
"repelStrength": 10,
|
||||
"linkStrength": 1,
|
||||
"linkDistance": 250,
|
||||
"scale": 0.08317427835927536,
|
||||
"close": false
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
{
|
||||
"main": {
|
||||
"id": "59f0bd68c638b9ae",
|
||||
"type": "split",
|
||||
"children": [
|
||||
{
|
||||
"id": "6e3e7f0212dd6d2e",
|
||||
"type": "tabs",
|
||||
"children": [
|
||||
{
|
||||
"id": "5e19c94f304a33d1",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "graph",
|
||||
"state": {},
|
||||
"icon": "lucide-git-fork",
|
||||
"title": "그래프 뷰"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"direction": "vertical"
|
||||
},
|
||||
"left": {
|
||||
"id": "b20f341b7d225db0",
|
||||
"type": "split",
|
||||
"children": [
|
||||
{
|
||||
"id": "76facd68bdc37a30",
|
||||
"type": "tabs",
|
||||
"children": [
|
||||
{
|
||||
"id": "697d93dc46e83f99",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "file-explorer",
|
||||
"state": {
|
||||
"sortOrder": "alphabetical",
|
||||
"autoReveal": false
|
||||
},
|
||||
"icon": "lucide-folder-closed",
|
||||
"title": "파일 탐색기"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "14386382787eb545",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "search",
|
||||
"state": {
|
||||
"query": "",
|
||||
"matchingCase": false,
|
||||
"explainSearch": false,
|
||||
"collapseAll": false,
|
||||
"extraContext": false,
|
||||
"sortOrder": "alphabetical"
|
||||
},
|
||||
"icon": "lucide-search",
|
||||
"title": "검색"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "6544f7f2d2bdb927",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "bookmarks",
|
||||
"state": {},
|
||||
"icon": "lucide-bookmark",
|
||||
"title": "북마크"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"direction": "horizontal",
|
||||
"width": 300
|
||||
},
|
||||
"right": {
|
||||
"id": "eb1afd59f22726e4",
|
||||
"type": "split",
|
||||
"children": [
|
||||
{
|
||||
"id": "cff2bf89b29bbdad",
|
||||
"type": "tabs",
|
||||
"children": [
|
||||
{
|
||||
"id": "a06f05e29da92edb",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "backlink",
|
||||
"state": {
|
||||
"collapseAll": false,
|
||||
"extraContext": false,
|
||||
"sortOrder": "alphabetical",
|
||||
"showSearch": false,
|
||||
"searchQuery": "",
|
||||
"backlinkCollapsed": false,
|
||||
"unlinkedCollapsed": true
|
||||
},
|
||||
"icon": "links-coming-in",
|
||||
"title": "백링크"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "461414a74ff42c5f",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "outgoing-link",
|
||||
"state": {
|
||||
"linksCollapsed": false,
|
||||
"unlinkedCollapsed": true
|
||||
},
|
||||
"icon": "links-going-out",
|
||||
"title": "나가는 링크"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2c463caabad51324",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "tag",
|
||||
"state": {
|
||||
"sortOrder": "frequency",
|
||||
"useHierarchy": true,
|
||||
"showSearch": false,
|
||||
"searchQuery": ""
|
||||
},
|
||||
"icon": "lucide-tags",
|
||||
"title": "태그"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "e863614ec11ec6c0",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "all-properties",
|
||||
"state": {
|
||||
"sortOrder": "frequency",
|
||||
"showSearch": false,
|
||||
"searchQuery": ""
|
||||
},
|
||||
"icon": "lucide-archive",
|
||||
"title": "모든 속성"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "0f1cc972aeac180a",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "outline",
|
||||
"state": {
|
||||
"followCursor": false,
|
||||
"showSearch": false,
|
||||
"searchQuery": ""
|
||||
},
|
||||
"icon": "lucide-list",
|
||||
"title": "개요"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"direction": "horizontal",
|
||||
"width": 300,
|
||||
"collapsed": true
|
||||
},
|
||||
"left-ribbon": {
|
||||
"hiddenItems": {
|
||||
"switcher:빠른 전환기 열기": false,
|
||||
"graph:그래프 뷰 열기": false,
|
||||
"canvas:새 캔버스 만들기": false,
|
||||
"daily-notes:오늘의 일일 노트 열기": false,
|
||||
"templates:템플릿 삽입": false,
|
||||
"command-palette:명령어 팔레트 열기": false,
|
||||
"bases:새 베이스 생성하기": false
|
||||
}
|
||||
},
|
||||
"active": "5e19c94f304a33d1",
|
||||
"lastOpenFiles": [
|
||||
"Systemic_Simulation_Principles.md",
|
||||
"DevOps_Environment_Setup.md",
|
||||
"Separation_of_Concerns.md",
|
||||
"Single_Source_of_Truth.md",
|
||||
"WebWorker_Performance.md"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: 개발 환경 및 실행 프로세스 관리 (DevOps & Setup)
|
||||
category: DevOps
|
||||
tags: [DevOps, Environment, CI/CD, Process Management]
|
||||
created: 2026-04-20
|
||||
---
|
||||
|
||||
# 개발 환경 및 실행 프로세스 관리
|
||||
|
||||
## 🎯 개요 (Overview)
|
||||
코딩 완성도만큼이나 중요한 **실행 환경(Runtime Environment)**과 **설정 파일(Configuration)**의 무결성을 확보하여, '내 컴퓨터에선 되는데 왜 저기선 안 되지?'라는 문제를 해결하는 프로세스입니다.
|
||||
|
||||
## 🚀 필수 체크리스트 (Checklist)
|
||||
- **의존성 관리**: `npm install` 등 패키지 무결성 확인.
|
||||
- **물리적 파일 구조**: `index.html` 등 필수 진입점 파일 존재 확인.
|
||||
- **보안 및 권한**: OS 레벨의 실행 정책(`Execution Policy`) 및 권한 설정.
|
||||
|
||||
## 💡 레슨 런 (Lesson Learned)
|
||||
> [!NOTE]
|
||||
> **"운영 환경에 대한 이해는 코딩 능력의 절반이다."**
|
||||
> 논리적 로직의 완성뿐만 아니라, 그것이 실제로 구동되는 물리적 인프라 설정을 문서화하고 자동화하는 능력이 필수적입니다.
|
||||
|
||||
## 🔗 연결된 지식
|
||||
- [[Systemic_Simulation_Principles]]
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: 시스템 아키텍처와 관심사 분리 (Separation of Concerns)
|
||||
category: Software Architecture
|
||||
tags: [Architecture, SoC, Modular Design, Design Pattern]
|
||||
created: 2026-04-20
|
||||
---
|
||||
|
||||
# 시스템 아키텍처와 관심사 분리 (SoC)
|
||||
|
||||
## 🎯 개요 (Overview)
|
||||
복잡한 소프트웨어 시스템을 역할별로 구분된 독립적인 모듈로 나누어, 유지보수성과 확장성을 극대화하는 설계 철학입니다.
|
||||
|
||||
## 🚀 계층구조 예시 (Layering Example)
|
||||
1. **Logic Engine**: 순수 비즈니스 로직 및 규칙 수행 (예: `gameWorker.js`)
|
||||
2. **State Manager**: 데이터의 중앙 집중 처리 (예: `TetrisGame.jsx`)
|
||||
3. **View Layer**: 사용자 인터페이스 표현 및 렌더링 (예: React Components)
|
||||
|
||||
## 💡 레슨 런 (Lesson Learned)
|
||||
> [!IMPORTANT]
|
||||
> **"코드의 경계가 명확할 때 시스템은 비로소 건강해진다."**
|
||||
> 기능을 추가할 때 기존 코드를 수정하기보다 새로운 모듈을 덧붙일 수 있는 구조를 고민해야 합니다.
|
||||
|
||||
## 🔗 연결된 지식
|
||||
- [[WebWorker_Performance]]
|
||||
- [[Single_Source_of_Truth]]
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: 상태 관리의 단일 진실 공급원 (Single Source of Truth)
|
||||
category: Software Architecture
|
||||
tags: [State Management, Data Consistency, Redux, Architecture]
|
||||
created: 2026-04-20
|
||||
---
|
||||
|
||||
# 상태 관리의 단일 진실 공급원 (Single Source of Truth)
|
||||
|
||||
## 🎯 개요 (Overview)
|
||||
시스템의 핵심 데이터를 중앙 집중식으로 관리하여, 데이터 불일치(Inconsistency) 현상을 원천 차단하고 예측 가능한 데이터 흐름을 확보하는 설계 원칙입니다.
|
||||
|
||||
## 🚀 주요 원칙 (Key Principles)
|
||||
- **단일 지점 정의 (Defined at Single Point)**: 상태는 오직 한 곳에서만 정의되고 관리되어야 합니다.
|
||||
- **예측 가능성 (Predictability)**: 상태 변경은 정해진 규칙(Action/Setter)을 통해서만 발생하여 디버깅을 용이하게 합니다.
|
||||
|
||||
## 💡 레슨 런 (Lesson Learned)
|
||||
> [!TIP]
|
||||
> **"상태는 오직 한 곳에서만 정의하고, 모든 로직은 그 상태를 읽고 쓰는 방식으로 동작해야 한다."**
|
||||
> 코드의 파편화를 막기 위해 데이터의 책임 범위(Responsibility)를 명확히 하는 것이 대규모 프로젝트 성공의 열쇠입니다.
|
||||
|
||||
## 🔗 연결된 지식
|
||||
- [[Separation_of_Concerns]]
|
||||
- [[Domain-Driven Design (DDD)]]
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: 시스템 시뮬레이션 설계 원리
|
||||
category: Systemic Modeling & Fun
|
||||
tags: [Simulation, Physics Engine, Systemic Modeling, Ruleset]
|
||||
created: 2026-04-20
|
||||
---
|
||||
|
||||
# 시스템 시뮬레이션 설계 원리
|
||||
|
||||
## 🎯 개요 (Overview)
|
||||
현실 세계의 물리 법칙이나 비즈니스 규칙을 수학적으로 정의하고, 이를 절대적으로 우회할 수 없는 시스템 내의 법(Law)으로 구축하는 설계 기법입니다.
|
||||
|
||||
## 🚀 핵심 메커니즘 (Mechanisms)
|
||||
- **규칙 강제 (Ruleset Enforcement)**: 모든 상태 변화는 사전에 정의된 물리 엔진 함수(`checkCollision` 등)를 거쳐야만 합니다.
|
||||
- **수학적 모델링**: 변화를 시각적 묘사가 아닌 데이터와 수식으로 먼저 증명합니다.
|
||||
|
||||
## 💡 레슨 런 (Lesson Learned)
|
||||
> [!TIP]
|
||||
> **"모든 시뮬레이션은 수학적 규칙을 절대 우회할 수 없도록 강제해야 한다."**
|
||||
> 이를 통해 단순한 게임을 넘어 자율주행, 물리 엔진 등 고도의 결정론적 시스템 모델링이 가능해집니다.
|
||||
|
||||
## 🔗 연결된 지식
|
||||
- [[WebWorker_Performance]]
|
||||
- [[Separation_of_Concerns]]
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: WebWorker를 이용한 고성능 아키텍처 설계
|
||||
category: Web & Performance
|
||||
tags: [Web Worker, Concurrency, Performance, UI responsiveness]
|
||||
created: 2026-04-20
|
||||
---
|
||||
|
||||
# WebWorker를 이용한 고성능 아키텍처 설계
|
||||
|
||||
## 🎯 개요 (Overview)
|
||||
실시간 상태 변화가 매우 빈번한 애플리케이션(예: 게임, 시뮬레이션)에서 UI 스레드와 복잡한 연산 로직을 분리하여 **프레임 드롭(Jank)**을 방지하는 아키텍처 설계 기법입니다.
|
||||
|
||||
## 🚀 주요 원칙 (Key Principles)
|
||||
- **스레드 분리 (Thread Isolation)**: 무거운 계산은 백그라운드 스레드(Web Worker)에서 수행하고, 메인 스레드는 렌더링에만 집중합니다.
|
||||
- **메시징 기반 통신 (Messaging Architecture)**: `postMessage`와 `onmessage`를 통해 비동기적으로 데이터를 주고받아 결합도를 낮춥니다.
|
||||
|
||||
## 💡 레슨 런 (Lesson Learned)
|
||||
> [!IMPORTANT]
|
||||
> **"성능 병목 현상은 종종 '스레딩(Threading)'의 문제이다."**
|
||||
> 복잡한 물리 계산이나 루프가 UI 응답성을 해치지 않도록, 연산 엔진을 완전히 별도의 스레드로 격리하는 것이 부드러운 UX의 핵심입니다.
|
||||
|
||||
## 🔗 연결된 지식
|
||||
- [[Separation_of_Concerns]]
|
||||
- [[Systemic_Simulation_Principles]]
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "tetris-game",
|
||||
"version": "1.0.0",
|
||||
"description": "A high-performance Tetris game using Web Workers.",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "5.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
const TetrisGame = () => {
|
||||
// [State Management] 전역 상태: 보드와 현재 블록 정보를 저장합니다.
|
||||
const [gameState, setGameState] = useState({ board: [], piece: null });
|
||||
const [workerStatus, setWorkerStatus] = useState("Initializing...");
|
||||
|
||||
useEffect(() => {
|
||||
// 1. Web Worker 초기화 및 통신 설정
|
||||
const worker = new Worker(new URL('./gameWorker.js', import.meta.url), { type: 'module' });
|
||||
setWorkerStatus("Running...");
|
||||
|
||||
// 2. 워커로부터 메시지를 수신할 리스너 등록 (Web Worker의 핵심)
|
||||
worker.onmessage = (e) => {
|
||||
const data = e.data;
|
||||
if (data.type === 'READY' || data.type === 'UPDATE') {
|
||||
// 받은 데이터를 전역 상태로 업데이트합니다. (Single Source of Truth 원칙 준수)
|
||||
setGameState({ board: data.board, piece: data.piece });
|
||||
} else if (data.type === 'ERROR') {
|
||||
console.error("Game Worker Error:", data.message);
|
||||
}
|
||||
};
|
||||
|
||||
// 3. 워커에 초기화 명령 전송 (Worker 시작)
|
||||
worker.postMessage({ type: 'INIT' });
|
||||
|
||||
// Cleanup 함수: 컴포넌트 언마운트 시 워커를 종료합니다.
|
||||
return () => {
|
||||
worker.terminate();
|
||||
};
|
||||
}, []); // 마운트 시 한 번만 실행 (useEffect)
|
||||
|
||||
|
||||
// [Game Loop] 게임 루프 관리 (핵심 성능 최적화 부분)
|
||||
const handleGameLoop = useCallback(() => {
|
||||
if (!gameState.piece) return;
|
||||
|
||||
// 1초에 1번씩 gravity step을 요청합니다.
|
||||
const worker = new Worker(new URL('./gameWorker.js', import.meta.url), { type: 'module' });
|
||||
worker.postMessage({ type: 'MOVE_STEP', payload: {} });
|
||||
|
||||
// 다음 프레임에서 다시 이 함수를 호출하여 지속적인 움직임을 만듭니다.
|
||||
requestAnimationFrame(handleGameLoop);
|
||||
}, [gameState.piece]); // piece가 있을 때만 루프 시작
|
||||
|
||||
useEffect(() => {
|
||||
if (gameState.piece) {
|
||||
const animationFrameId = requestAnimationFrame(handleGameLoop);
|
||||
return () => cancelAnimationFrame(animationFrameId);
|
||||
}
|
||||
}, [gameState.piece, handleGameLoop]);
|
||||
|
||||
|
||||
// 렌더링 로직: 게임 보드와 블록을 시각화합니다.
|
||||
const renderBoard = () => {
|
||||
return (
|
||||
<div style={styles.board}>
|
||||
{/* React는 배열의 배열을 순회하며 각 셀에 CSS 클래스를 적용하여 렌더링합니다. */}
|
||||
{gameState.board.map((row, y) => (
|
||||
<div key={y} style={{ display: 'flex' }}>
|
||||
{row.map((cell, x) => (
|
||||
<div
|
||||
key={`${x}-${y}`}
|
||||
style={cell > 0 ? styles.occupied : {}}
|
||||
title={`(${x}, ${y})`}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<h1>Tetris Clone: High-Performance Engine</h1>
|
||||
<p>Status: {workerStatus}</p>
|
||||
{renderBoard()}
|
||||
<button onClick={() => console.log("Game Controls Here!")} disabled={!gameState.piece}>
|
||||
(이동/회전 버튼)
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
container: { padding: '20px', fontFamily: 'Arial, sans-serif' },
|
||||
board: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
border: '4px solid #333',
|
||||
width: '250px'
|
||||
},
|
||||
occupied: { backgroundColor: '#ccc', border: '1px solid #aaa' }
|
||||
};
|
||||
|
||||
export default TetrisGame;
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @file Web Worker: Tetris Game Logic Engine (The Core Rulebook)
|
||||
* 이 파일은 메인 스레드와 분리되어 실행되며, 게임의 모든 물리적 규칙을 처리합니다.
|
||||
*/
|
||||
|
||||
let gameBoard = []; // 20x10 Grid representation
|
||||
const BOARD_WIDTH = 10;
|
||||
const BOARD_HEIGHT = 20;
|
||||
|
||||
// 초기 보드 상태 설정 (빈 공간)
|
||||
function initializeBoard() {
|
||||
gameBoard = Array(BOARD_HEIGHT).fill(null).map(() => Array(BOARD_WIDTH).fill(0));
|
||||
}
|
||||
|
||||
// 블록 생성 로직 (랜덤 모양, 랜덤 위치)
|
||||
function generatePiece() {
|
||||
// 실제로는 다양한 T, L, I, O 등의 형태를 정의해야 함. 여기서는 단순 예시로 대체합니다.
|
||||
return { shape: [/* 4x4 matrix for a piece */], color: Math.random(), x: Math.floor(Math.random() * (BOARD_WIDTH - 3)), y: 0 };
|
||||
}
|
||||
|
||||
// 충돌 감지 로직 (Collision Detection)
|
||||
function checkCollision(piece, newX, newY) {
|
||||
for (let row = 0; row < piece.shape.length; row++) {
|
||||
for (let col = 0; col < piece.shape[row].length; col++) {
|
||||
if (piece.shape[row][col] !== 0) {
|
||||
const boardX = newX + col;
|
||||
const boardY = newY + row;
|
||||
|
||||
// 경계 체크 또는 이미 블록이 있는 경우 충돌 발생
|
||||
if (boardX < 0 || boardX >= BOARD_WIDTH || boardY >= BOARD_HEIGHT || gameBoard[boardY][boardX] !== 0) {
|
||||
return true; // Collision detected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 게임의 핵심 로직: 한 스텝 진행 (Gravity Step)
|
||||
function movePiece(dx, dy) {
|
||||
let newX = piece.x + dx;
|
||||
let newY = piece.y + dy;
|
||||
|
||||
if (!checkCollision(piece, newX, newY)) {
|
||||
// 1. 상태 업데이트: 보드의 좌표와 블록을 이동시킵니다.
|
||||
// 2. 충돌 체크 및 고정: 바닥 또는 다른 블록에 닿으면, 해당 위치를 게임 보드에 영구적으로 기록합니다.
|
||||
// 3. 라인 제거 (Line Clearing): 가득 찬 행(Row)을 찾아 지우고 위쪽 행들을 아래로 떨어뜨립니다.
|
||||
return { success: true, board: [...gameBoard], piece: {...piece} };
|
||||
} else {
|
||||
return { success: false, message: "Collision" };
|
||||
}
|
||||
}
|
||||
|
||||
// 메인 게임 루프 (Game Loop)
|
||||
self.onmessage = function(e) {
|
||||
const { type, payload } = e.data;
|
||||
|
||||
switch (type) {
|
||||
case 'INIT':
|
||||
initializeBoard();
|
||||
// 초기 블록 생성 및 상태 전송
|
||||
self.postMessage({ type: 'READY', board: gameBoard, piece: generatePiece() });
|
||||
break;
|
||||
case 'MOVE_STEP':
|
||||
// 실제 게임 루프의 핵심 로직 호출
|
||||
const result = movePiece(payload.dx || 0, payload.dy || 1);
|
||||
self.postMessage({ type: 'UPDATE', board: result.board, piece: result.piece });
|
||||
break;
|
||||
case 'ROTATE':
|
||||
// 블록 회전 로직 구현 (Omitted for brevity)
|
||||
break;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user