보안 공부를 시작하면서 IP, Port, TCP, NAT 같은 단어를 많이 접하게 됐다. 그런데 각각의 개념을 공부하기 전에 먼저 네트워크가 무엇인지 부터 이해할 필요가 있었다. 네트워크란? 네트워크(Network)는 여러 장치가 서로 데이터를 주고받을 수 있도록 연결된 구조 라고 이해하면 된다. 예를 들어 집에서 PC, 스마트폰, 노트북이 하나의 공유기에 연결되어 있다면 이 장치들은 하나의 네트워크에 속해 있다. PC ─────┐ Phone ──┼── 공유기 ─── 인터넷 Laptop ─┘ 네트워크에 연결된 PC, 서버, 스마트폰 같은 장치를 Host 라고 부른다. LAN LAN은 Local Area Network 의 약자로 비교적 좁은 범위의 네트워크를 의미한다. 대표적으로 집 회사 학교 PC방 같은 환경이 있다. 집에서 같은 공유기에 연결된 PC와 스마트폰은 같은 LAN에 있다고 볼 수 있다. WAN WAN은 Wide Area Network 의 약자로 서로 멀리 떨어진 네트워크를 연결하는 넓은 범위의 네트워크다. 예를 들어 서울 사무실과 부산 사무실의 네트워크를 연결한다면 WAN의 개념으로 볼 수 있다. 서울 LAN │ WAN │ 부산 LAN 인터넷 역시 전 세계의 수많은 네트워크가 서로 연결되어 만들어진 거대한 네트워크다. Router와 Switch 네트워크에서 자주 등장하는 장비가 Router와 Switch다. Switch 같은 네트워크 내부의 장치들을 연결한다. PC ────┐ Server ─┼── Switch Laptop ─┘ Router 서로 다른 네트워크를 연결하고 데이터가 어느 방향으로 이동해야 하는지 결정한다. 집에서 사용하는 공유기도 Router 역할을 포함하고 있다. Linux에서 네트워크 확인하기 Linux에서는 다음 명령어로 네트워크 정보를 확인할 수 있다. ip addr 현재 Network Interface와 IP 주소를 확인할 수 있다. ip route 패킷이 어느 경로로 이동하는지 확인할 수 있다. 특히 default via 뒤에 나오는 주소는 보통 기본 Gateway다. 보안에서는 왜 중요할까? 보안에서는 대부분의 통신이 네트워크를 통해 이루어진다. 서버에 누가 접속했는지, 어떤 IP와 Port를 사용했는지, 어떤 서비스가 외부에 열려 있는지를 확인하려면 네트워크 구조를 이해해야 한다. 앞으로 공부할 IP → Subnet → Gateway → Routing → NAT → TCP/UDP → Port → Firewall 도 결국 네트워크에서 데이터가 어떻게 이동하는지를 이해하기 위한 내용이라고 생각한다. 다음 글에서는 IP Address와 Public IP / Private IP 를 정리해보려고 한다.
프로젝트 생성 node.js 프로젝트 생성 -> npx create-next-app@latest 프로젝트이름 프로젝트 셋팅 (공부용) TypeScript → Yes ESLint → Yes Tailwind → Yes src/ → No App Router → Yes 권장 설정을 체크했다. Need to install the following packages: create-next-app@16.3.8 Ok to proceed? (y) y √ Would you like to use the recommended Next.js defaults? » Yes, use recommended defaults TypeScript 타입 안정성 ESLint is not Formatter ESLint ≠ 포매터라는 것. Prettier → 코드 모양을 예쁘게 정리 ESLint → 코드가 올바른가? → 문제 있는 패턴인가? - 이 변수 안 쓰고 있는데? - React에서는 더 중요 -> Hook을 잘못 사용하는 패턴이나 의존성 문제 등을 검사 프론트엔드 스타일링 방식 특징 이번 프로젝트에서 일반 CSS .css 파일에 스타일 작성 가능하지만 파일을 오가야 함 CSS Modules 컴포넌트별 CSS 파일 + 클래스 이름 충돌 방지 깔끔하지만 파일이 늘어남 styled-components JS/TS 안에서 CSS 작성 동적 스타일에 좋지만 별도 라이브러리 Tailwind className 에 유틸리티 조합 이번 학습 프로젝트에 적합 처음 배우는 프로젝트에서 이것들을 여러 개 섞으면 학습 포인트가 흐려지니까 하나를 골라서 쓰는 게 좋다 -> Tailwind CSS만 사용 프론트엔드 스타일링 │ ├─ CSS를 직접 작성하는 계열 │ ├─ CSS / CSS Modules │ └─ Sass(SCSS) │ ├─ 유틸리티 CSS 계열 ("스타일을 내가 조립한다.") │ ├─ Tailwind CSS │ └─ UnoCSS │ └─ UI 컴포넌트 ("React UI 컴포넌트를 가져다 쓴다.") / CSS 프레임워크 계열 ("미리 만들어진 CSS 스타일을 가져다 쓴다.") ├─ Bootstrap ├─ Chakra UI ├─ MUI ├─ Ant Design └─ Mantine /src 폴더 사용 안함. 작은 학습 프로젝트 → src 없어도 됨 src는 코드를 한 단계 안쪽으로 넣어서 정리하는 폴더일 뿐 -> 왜 src를 쓰나? 큰 프로젝트의 경우 디렉토리 구조 project/ ├── app/ ├── components/ ├── hooks/ ├── lib/ ├── public/ ├── tests/ ├── scripts/ ├── config/ ├── package.json └── ... project/ ├── src/ ---> 복잡해지니까 실제 애플리케이션 코드를 한단계 안쪽으로 정리 │ ├── app/ │ ├── components/ │ ├── hooks/ │ └── lib/ ├── public/ ├── tests/ ├── package.json └── ... Pages Router vs App Router Pages Router App Router 기준 디렉터리 pages/ app/ 등장 시기 기존 방식 최신 방식 서버 컴포넌트 기본 지원 X 기본 지원 Layout 직접 구성하는 경우가 많음 layout.tsx 로 자연스럽게 구성 데이터 처리 기존 Next.js 방식 Server Component 중심 현재 신규 프로젝트 유지보수에는 많이 존재 신규 프로젝트에서 주로 사용 2016 ~ 2022 │ │ Pages Router 시대 │ pages/가 사실상 기본 │ ├── Next.js 13 (2022.10) │ app/ 등장 │ → 아직 Beta │ ├── Next.js 13.4 (2023.05) │ ⭐ App Router Stable │ → 신규 프로젝트에 App Router 권장 │ 2023 ~ 현재 │ │ App Router가 새로운 기본 방향 │ Pages Router도 계속 지원 │ └── 2026 현재 → 새 프로젝트: App Router 권장 → 기존 프로젝트: Pages Router도 정상적으로 사용 Pages Router pages/ ├── index.tsx ├── products.tsx └── products/ └── [id].tsx / → index.tsx /products → products.tsx /products/1 → [id].tsx App Router : Pages Router와 URL 구조는 비슷하지만 컴포넌트와 서버 기능을 결합해서 설계하기 편함 app/ ├── page.tsx ├── products/ │ ├── page.tsx │ └── [id]/ │ └── page.tsx └── layout.tsx 예를 들어 MES가 있다고 하자. ┌─────────────────────────────┐ │ 회사 로고 사용자 정보 │ ├─────────┬───────────────────┤ │ 메뉴 │ │ │ 생산관리 │ 화면 내용 │ │ 재고관리 │ │ │ 출고관리 │ │ └─────────┴───────────────────┘ 왼쪽 메뉴와 위쪽 헤더는 모든 페이지에서 계속 유지를 원함. App Router에서는 이걸 아주 자연스럽게 표현: app/ ├── layout.tsx ← 전체 공통 ├── page.tsx ← / │ └── mes/ ├── layout.tsx ← MES 공통 ├── production/ │ └── page.tsx └── inventory/ └── page.tsx layout │ ├── 생산관리 페이지 │ └── 재고관리 페이지 이런 페이지 구조 자체를 폴더 구조로 표현 가능하다. Pages Router에도 공통 레이아웃을 만들 수 있 지만 구조적으로 App Router만큼 자연스럽지는 않았다. 예를 들어 예전에는 _app.tsx를 사용해서 전체 페이지를 감싸는 구조를 만들었다. export default function App({ Component, pageProps }) { return ( <Layout> <Component {...pageProps} /> </Layout> ); } 전체 사이트 공통 Layout 은 만들기 쉬운데, MES 공통 Layout ├── 생산관리 ├── 재고관리 관리자 공통 Layout ├── 사용자 관리 └── 권한 관리 처럼 여러 단계의 레이아웃을 자연스럽게 중첩하는 구조는 불편했다. Next.js가 App Router를 만든 중요한 이유 중 하나 현재 App Router를 배우는 가장 중요한 이유 App Router에서는 기본적으로 컴포넌트가 Server Component이다. // app/products/page.tsx export default async function ProductsPage() { const products = await getProducts(); return ( <div> ... </div> ); } // 서버에서 데이터를 가져와서 화면을 만들어 보낼 수 있다. 버튼 클릭이나 useState 같은 브라우저에서 동작해야 하는 것은 따로 지정 : "use client"; // Client Component import { useState } from "react"; 즉 App Router는 단순히 "pages를 app으로 바꾼 것"이 아니라, React Server Components를 중심으로 Next.js의 구조 자체를 새로 만든 것이다. 실행 확인 cd 프로젝트 npm run dev
DAY 36 | 개인 프로젝트 — viewmodel & riverpod (4) 작업 일정 #35 [state] ProfileNotifier 및 프로필/스케치 아카이브 구현 #36 [state] SettingsNotifier 및 계정/앱 설정 관리 구현 #35 [state] ProfileNotifier 및 프로필/스케치 아카이브 구현 프로필 화면까지 하다 보니 친구 목록 화면이랑 사용자 검색 화면이랑 프로필 화면에서 코드 중복이 발생하는 것을 발견했다. 하는 일이 완전히 동일하지는 않지만 거의 비슷한 녀석들. Gemini를 통해 비교해 보자면 다음과 같다. ViewModel 들고 있는 상태 (State) 친구 수락/취소 시 일어나는 일 FriendsViewModel List<UserSummary> friends List<UserSummary> receivedRequests 받은 요청 리스트에서 제거하고, 친구 리스트에 추가 UserSearchViewModel Set<String> friendIds Set<String> sentRequestIds Set<String> receivedRequestIds ID Set에서 add / remove UserProfileViewModel Friendship? friendship List<SketchPost> sketches 단일 관계 객체 변경 및 상대방 스케치 목록 즉시 조회 FriendsViewModel , UserSearchViewModel , UserProfileViewModel 세 곳을 비교해 보면 겉모습은 똑같이 friendshipRepository.acceptFriendRequest(...) 등을 호출합니다. 하지만 호출 결과를 자기 State에 반영하는 방식(State Mutation) 이 화면마다 완전히 다릅니다. 즉, "화면마다 보여줘야 하는 데이터 형태가 다르기 때문에, 각 ViewModel이 자기 화면의 상태를 즉시 갱신(낙관적 UI 업데이트)하느라" Repository를 부르는 코드가 복붙처럼 중복된 것입니다. 현재 방식의 잠재적 문제점: "상태 불일치 (State Inconsistency)" 이 방식은 코드가 중복될 뿐만 아니라 더 큰 문제가 있습니다: 프로필 화면( UserProfileScreen )에서 친구를 맺거나 차단했는데, 뒤로가기를 눌러 검색 화면이나 친구 목록 화면으로 돌아갔을 때, 만약 깜빡하고 ref.invalidate(...) 를 호출하지 않으면 화면마다 친구 상태가 제각각 다르게 보이는 '상태 불일치' 버그가 생깁니다. Flutter 및 Riverpod 실무에서 이 중복을 말끔히 해결하는 대표적인 방법은 "조회(Query)와 변경(Command)의 분리 (CQS 패턴)" 입니다. 관련 개념 단일 진실 공급원 (Single Source of Truth, SSOT) 원칙 관심사의 분리 (Separation of Concerns)와 계층화 (Layering) 단방향 데이터 흐름 (Unidirectional Data Flow, UDF) 및 CQS 패턴 코드 유지보수성 및 실무 규모 확장성 (Scalability) 따라서 친구 요청을 보내고 취소하고 수락하고 거절하는 등의 기능은 ViewModel에서 개별적으로 작성하는 게 아니라 UseCase로 따로 빼기로 했다. "1 UseCase = 1 Action" 원칙에 따라 친구 요청, 친구 요청 취소, 친구 요청 수락, 친구 요청 거절, 친구 삭제, 차단, 차단 해제에 대한 UseCase를 작성한다. 상대와의 관계에 따라 화면에 보이는 게 달라져 조건문 분기를 사용했다. 한 줄 소개 및 스케치 게시물은 친구 공개이므로 친구가 아닌 자에게는 보여주지 않는다. 한 줄 소개 위치에 친구가 아닌 경우에는 친구 요청 버튼이 뜬다. 요청 상태에 따라 친구 요청, 수락 및 거절, 요청 취소 버튼이 뜬다. 친구일 경우에는 실수로 친구 삭제를 누르지 않도록 친구 삭제 버튼은 메뉴 안에 숨겨져 있다. route가 꼬인 부분이 있어서 살펴 보니 검색해서 얻은 자료에서 사용하던 방식이 내 프로젝트와 차이가 있는데 제대로 검토하지 않고 사용한 여파였다. 어디에는 UID 기반으로 되어 있고 어디에는 username 기반으로 되어 있는 것을 정리해 주니 문제가 해결되었다. 하는 김에 나중에 리팩토링 하려고 했던 route 작업도 같이 처리했다. #36 [state] SettingsNotifier 및 계정/앱 설정 관리 구현 Mock model이 남아 있는 [차단한 사용자 괸리]부터 작업하였다. 여기서도 다른 곳처럼 ViewModel에서 사용할 State class를 만들고 작업을 하다가 엎엇다. 차단 해제에 대한 것도 FriendshipActionHandler 에 묶어 버리는 게 나을 것 같다. 당장은 차단된 사용자는 검색에서 빠져 있기에 [차단한 사용자 관리]에서밖에 차단 해제를 안 하겠지만, 딥링크로 사용자 프로필에 접속하는 경우가 발생할 경우 차단된 사용자 프로필 메뉴에서도 차단 해제를 할 수 있도록 하는 게 UX적으로 더 나으니까. ViewModel에서 차단 해제를 처리했다면 차단한 사용자 목록 blockedUsers 와 작업 진행 중 여부 isProcessing 을 담는 State class가 필요했겠지만 작업 수행을 FriendshipActionHandler 에서 하면 blockedUsers 만 있으면 되므로 따로 State class를 생성하지 않고 List<UserSummary> 를 사용하기로 했다. [차단된 사용자 관리] 페이지를 작업하다 보니 검색에서는 차단된 사용자를 제외했지만 차단된 사용자가 친구의 스케치에 댓글을 달았을 때에 대한 처리가 되어 있지 않다는 것을 인지했다. 삭제된 댓글과 마찬가지로 답글이 없다면 화면에서 완전히 가리고 답글이 있다면 차단된 사용자의 댓글이라고 표시되도록 수정하였다. 기본적으로 모든 View가 ViewModel과 연결되고 ViewModel이 Repository와 연결되며 Repository가 Service로 연결되는 구조가 MVVM 아키텍처의 흐름이다. 경우에 따라서는 UseCase가 끼기도 하지만. 하여간 기본적으로는 이를 따르되, 설정 화면에서는 예외를 두기로 했다. 독자적인 비즈니스 상태를 갖는 게 아니라 이미 메모리에 존재하는 전역 로그인 세션의 단일 속성을 변경하는 역할을 수행하므로, 설정 화면마다 ViewModel을 두면 사용자 세션에 대한 상태의 이중화 및 동기화 오버헤드가 발생할 수 있다는 이슈가 있다. 교과서적인 아키텍처 100%보다는 상황에 따라 적절한 trade-off를 고려하여 조정하는 것이 낫다는 판단으로, MVVM 구조의 완성보다는 단일 진실 공급원 원칙을 우선시하기로 했다. 일단은 소셜 로그인을 배제한 상태로 설정 기능을 마무리하고, 소셜 로그인 기능을 추가할 때 설정 화면의 기능 구현을 마무리하도록 하겠다. 최초로 소셜 로그인을 하였을 때 필요한 페이지도 새로 만들어야 하고 처리할 게 좀 있으니 설정 화면 기능 구현과 별개로 따로 빼는 게 나을 것 같다. 이용약관 및 개인정보 처리방침도 그 이후로 넘긴다. >>> GitHub Repository at this point (111b9df)
소집해제도 다가오고 (사실 아님) 동료도 퇴사하고 적적하다. 퇴근하자마자 집에와서 카카오 기출문제 2문제를 풀었다. 한개는 쉬웠고 이 문제는 레벨 1이라길래 만만하게 봤는데 정말 어려웠다. 처음에는 재밌었다가 나중에 때려치우고 싶은 마음을 겨우겨우 마음 다잡고했다. 평소에 런닝으로 단련된 내가 아니였다면 진작 GPT한테 문제 돌려봤을 것이다. 참고로 난 3km를 30분에 뛴다 어찌 어렵게 풀고나서 GPT한테 검수 요청하니 왜이렇게 어렵게 풀었냐고 뭐라해서 답지 보니깐 진짜 쉽게 풀더라. 요즘시대에 왜 알고리즘을 안보는지 더 체감하게된 요즘이였다. 거두절미하고 바로 들어가보자. 문제 링크 : https://school.programmers.co.kr/learn/courses/30/lessons/468370 카카오톡은 메시지의 일부를 가려두는 스포 방지 기능을 제공하는데, 스포 방지 기능을 적용한 메시지를 왼쪽에서부터 오른쪽으로 클릭하면서 공개되는 단어들 중, 중요한 단어는 몇개인지를 알아내는 문제이다. 중요한 단어라 함은 다음과 같다. 스포 방지 단어여야 합니다. 메시지의 스포 방지 구간이 아닌 구간(각 구간의 앞·사이·뒤 포함)에 등장한 적이 없어야 합니다. 이전에 공개된 스포 방지 단어와 중복되지 않아야 합니다. 여기서 스포 방지 단어란, 단어의 문자 하나 이상이 스포 방지 구간으로 포함된 단어를 뜻한다. 한마디로 중요한 단어 후보자들이다. 아이디어 핵심은 스포 방지 단어 를 알아내는 것이고 단어는 사이에 공백 으로 구분 지어진다. 우선 메시지에서 공백의 Index를 알아내고, 스포 방지로 가려진 영역인 spoiler_range 로 spoiler_range 의 시작 구간보다 앞에 있고, spoiler_range 의 끝나는 구간보다 뒤에 있는 공백 간의 공간을 구하면 스포 방지 단어 를 구할 수 있다. 그 후 해당 단어가 스포 방지 구간이 아닌 지역에 존재하지 않는가 significWord 에 포함되지 않은 단어인가 를 만족한다면 significWord 에 추가하고 마지막에 significWord 의 길이를 반환하면 된다. 이건 아주 간략화된 풀이 방법이고 테스트케이스가 많기때문에 엣지 케이스를 많이 고민해봐야한다. spoiler_range 가 공백만을 가르킨다거나, 문장이 공백으로 시작할 수 있다. 본인은 문장이 공백으로 시작할 수 있다는걸 고려하지 못했기때문에 많이 헤맸다. 시간복잡도 내가 푼 방식대로하면 O(N^2)이다. 문제 유형 그냥 구현 문제다. 지문을 꼼꼼히 읽고 엣지 케이스들을 최대한 고려해야 한다. 소감 구현 문제의 어려운 점은 이런 엣지 케이스를 고려하는 것에서 나오는것 같다. 위에 요즘시대에 알고리즘이라고 써놓기는 했지만, 문제 해결력과 구조화 능력은 절대 죽지 않는다. 생각의 흐름을 잘 안풀린다고해서 포기하지 않고 차례 차례 밟아나가 보는 것이 문제 해결의 첫 단추라고 느꼈다. 전체 코드 function solution(message, spoiler_ranges){ const messageArray = [...message]; const spaceIndexs= []; for(let i =0; i<messageArray.length; i++){ if(messageArray[i] === ' '){ spaceIndexs.push(i) } } const extractWordChunk = (range) => { const startWordChunkRangePoint = [...spaceIndexs].reverse().find((i)=> i<=range[0]) ?? -1; const endWordChunkRangePoint = spaceIndexs.find((i)=> i>=range[1]) ?? message.length; for(let i = startWordChunkRangePoint + 1 ; i<endWordChunkRangePoint; i++){ messageArray[i] = "*" } return message.slice(startWordChunkRangePoint + 1 ,endWordChunkRangePoint) } const candidateOfSignificWord = []; for(const range of spoiler_ranges){ const chunk = extractWordChunk(range); if(chunk === ''){ continue; } candidateOfSignificWord.push(...chunk.split(' ')) } const coveredMessage = messageArray.join('').split(' '); const significWord = []; for(const candidate of candidateOfSignificWord){ if(!significWord.includes(candidate) && !coveredMessage.includes(candidate)){ significWord.push(candidate) } } return significWord.length; } 참고) GPT의 풀이다. extractWordChunk 함수 자체를 아예 없애버렸다. function solution(message, spoiler_ranges) { const messageArray = [...message]; // 실제 스포가 걸린 문자만 *로 변경 for (const range of spoiler_ranges) { const start = range[0]; const end = range[1]; for (let i = start; i <= end; i++) { if (messageArray[i] !== ' ') { messageArray[i] = '*'; } } } const originalWords = message.split(' '); const coveredWords = messageArray.join('').split(' '); const normalWords = []; const spoilerWords = []; for (let i = 0; i < originalWords.length; i++) { // *가 있으면 이 단어는 스포 단어 if (coveredWords[i].includes('*')) { spoilerWords.push(originalWords[i]); } else { normalWords.push(originalWords[i]); } } const significWords = []; for (const word of spoilerWords) { if (normalWords.includes(word)) { continue; } if (significWords.includes(word)) { continue; } significWords.push(word); } return significWords.length; }```
MARDAN СТРОЙ выполняет ремонт квартир в Михайловске поэтапно, начиная с замера и подготовки подробной сметы. Специалисты помогают определить объём работ, подобрать материалы и согласовать условия до начала ремонта. Компания может выполнить комплексную отделку квартиры, включая инженерные системы и финишные покрытия.
[Outbreak Breaker] 스테이지 타임라인 시스템 확장, 일시정지 메뉴 로직 연결, GA 폴더 정리 오늘은 커밋 세 개를 올렸다. 어제 레이아웃까지 만들어둔 일시정지 메뉴에 실제 로직을 붙였고, GameplayAbility 애셋 폴더 구조를 미리 정리했고, 가장 크게는 스폰 매니저를 BGM/아이템까지 포괄하는 통합 타임라인 시스템으로 확장했다. 1. GameplayAbility 애셋 폴더 구조 정리 본격적으로 무기별 특수 스킬을 늘려나가기 전에 어빌리티 폴더부터 정리했다. GA_ForcedMovement 를 GA_Knockback 으로 리네임했다. 넉백이랑 끌어당기기(풀)를 나중에 분리할 계획이라, 이름부터 역할을 명확히 해두는 게 맞다고 판단했다. 끌어당기는 쪽은 나중에 "블랙홀" 같은 연출의 별도 GA로 만들 예정이다. GameplayAbilites/ 밑에 CC/ (GA_Stun, GA_Knockback)랑 Player/ (GA_Dodge) 폴더를 새로 만들어서 분류했다. 무기 공용으로 쓰는 GA_Weapon_Equip 은 Weapon 폴더 루트에 그대로 두고, 무기별로 GravityHammer/ , PlasmaRifle/ , SpreadShotgun/ , DefenseDrone/ , MagnetMine/ 서브폴더를 새로 만들어서 각 무기의 Active/Passive 어빌리티를 담았다. 지금 당장 쓰는 건 아니지만, 나중에 무기별 "선택 상태" 전용 특수 스킬을 추가할 때 자리가 미리 있어야 헷갈리지 않을 것 같아서 먼저 손봐뒀다. 2. 일시정지 메뉴 로직 연결 및 키 리바인딩 안전장치 어제 만든 레이아웃 기준으로 실제 토글 로직을 붙였다. UOBOverlayWidgetController::TogglePauseMenu() 를 추가해서, 위젯이 이미 떠 있으면 제거 + 게임/BGM 언폰즈, 없으면 생성 + 게임/BGM 폰즈를 처리하도록 했다. 생성 시 Z-Order는 10으로 깔아서, 그 위에 뜨는 설정 창(Z-Order 20)이 항상 일시정지 메뉴보다 위에 보이도록 했다. IA_PauseMenu 인풋 액션에 "Trigger When Paused"를 켜서, 게임이 멈춰 있어도 ESC 입력이 제대로 들어오도록 했다. EOBOverlayWidgetType 에도 PauseMenu 항목을 추가했다. 겸사겸사 키 리바인딩 안전장치도 추가했다. UOBGameSettings::LockedKeys (Escape, Tab, LeftCommand, Tilde, LeftAlt)랑 IsKeyLocked() 를 만들어서 RebindKey() 맨 앞에서 체크하게 했다 — 다른 액션을 이 예약된 키들로 리바인딩하지 못하게 막는 용도다. Pause 액션 자체는 Player Mappable Key Settings에 등록돼 있지 않아서 리바인딩 UI에는 애초에 노출되지 않는데, 이번 보호 로직은 그것과는 별개로 "다른 액션이 예약된 키를 가로채는 것"을 막는 데 집중한 거다. 계속하기/설정/타이틀로 돌아가기/게임 종료 네 항목 전부 로직 연결은 끝냈다. UI 쪽은 일단 지금 쓰던 기본 UMG 방식 그대로 두고, 나중에 CommonUI를 기존에 쓰던 MVVM 패턴이랑 합쳐서 적용할 계획이라 그 부분만 미정이다. 3. 스폰 매니저 → 스테이지 타임라인 시스템으로 확장 (오늘 제일 크게 건드린 부분) AOBSpawnManager 를 AOBStageTimelineManager 로 리네임하면서 역할 자체를 확장했다. 기존엔 적 스폰만 담당하던 걸, BGM 스케줄이랑 아이템 스폰까지 포괄하는 시간 기반 중앙 타임라인으로 바꿨다. 핵심은 TotalElapsedTime 이라는 클럭 하나를 적 스폰/BGM/아이템 타임라인이 전부 공유한다는 것 — 세 타임라인이 서로 다른 시계를 쓰면서 미묘하게 어긋나는 걸 원천적으로 방지하는 설계다. BGM 스케줄 : FRuntimeBGMRow (TriggerTime + BGMType + bTriggered)를 만들어서 지정 시점에 BGM이 딱 한 번만 발동하도록 했다. CheckBGMTimelineLoop() 에서 매 틱 테이블을 돌면서 조건을 체크한다. 기존엔 HandleGameStart() 에서 고정적으로 Zone1 BGM을 트는 식이었는데, 이제는 테이블 기반으로 시점별 BGM 전환을 데이터로 관리할 수 있다. 아이템 스폰 : FRuntimeItemSpawnRow (StartTime + SpawnAmount)로 시점/수량을 정하고, 실제 아이템 종류는 FOBItemSpawnEntry (ItemPoolTag + Weight) 가중치 랜덤 풀에서 누적합 방식으로 뽑는다( GetRandomItemPool() ). 적 스폰과 아이템 스폰이 좌표를 뽑는 로직이 사실상 똑같아서, GetRandomSpawnLocation(UsedIndices) 로 공용 함수화했다. "같은 웨이브 안에서 좌표가 완전히 겹치는 것만 회피" 하는 로직을 그대로 재사용. 스테이지별 데이터 구성(스폰 테이블/BGM 테이블/아이템 테이블/아이템 풀)은 FOBStageTimelineData 구조체 하나로 묶어서 UOBGameSettings::StageTimelineMap ( TMap<EOBLevelType, FOBStageTimelineData> )에 데이터 기반으로 등록했다. AOBGameModeBase::GetSelectedStageType() 게터를 추가해서, InitializeStageTimelineManager() 에서 현재 스테이지에 맞는 테이블/풀을 Settings에서 가져와 캐싱하고 3개 테이블 전부 bTriggered / LastSpawnTime 을 리셋하도록 했다. DataTable Row 전용 구조체( FRuntimeBGMRow , FRuntimeItemSpawnRow 등)는 OBStageTimelineRows.h 로 따로 분리했다. Settings 쪽이 알 필요 없는, 매니저 내부에서만 쓰는 타입이라 분리해두는 게 맞다고 판단. 리네임 작업 자체는 CoreRedirect를 쓰는 대신, 레벨에 배치된 기존 액터를 지우고 새 클래스로 다시 배치하는 방식으로 처리했다. 덤으로 EOBBGMType BGMType 필드가 초기화 안 돼 있어서 에디터 시작할 때마다 뜨던 EnumProperty 에러도 기본값(Title)을 명시해서 같이 고쳤다. 다음에 할 것 일시정지 메뉴 UI는 지금 UMG 방식 그대로 유지하다가, CommonUI를 기존 MVVM 패턴이랑 합쳐서 적용할 예정이라 시기는 미정 스테이지 타임라인 시스템 기반으로 실제 Stage1 데이터(BGM 스케줄, 아이템 스폰 타이밍) 밸런싱 테스트 젬 경험치 레이스 컨디션은 계속 인게임에서 재발 여부 확인 중
오늘 한 것 최종 프로젝트(3) 오늘 배운 점 & 느낀 점 오늘 배운 점은 통신 부분을 InGameClientBase 같은 추상 컴포넌트로 분리하면 씬 인스펙터에서 Mock과 실제 서버 클라이언트를 갈아 끼울 수 있고, 스크립트는 여러 씬이 공유해서 이름을 바꾸면 모든 씬의 컴포넌트가 같이 바뀌며, 패킷 수신 등록은 Awake에서 먼저 해 두고 네트워크 콜백은 메인 스레드로 넘겨 처리해야 한다는 걸 배웠다. 작은 문제점이 있었는데 실제 서버에서는 로그인과 매칭이 끝나야 내 userId와 gameId가 정해지는데, 게임 상태를 Awake에서 바로 초기화하고 있어서 매칭이 끝날 때까지 시작을 미루는 구조(Begin On Awake)를 따로 만들어야 했다. 내일 할 일 최종 프로젝트(3) 복습
코딩테스트에서 좌표와 선분이 등장하면 기울기를 먼저 떠올리기 쉽다. 예를 들어 두 선분이 평행한지 확인하려면 각 선분의 기울기를 계산해 비교할 수 있다. double slope1 = (double) dy1 / dx1; double slope2 = (double) dy2 / dx2; 하지만 이 방식에는 몇 가지 문제가 있다. dx == 0 인 수직선을 별도로 처리해야 한다. 실수 연산을 사용하면 오차를 고려해야 한다. 이럴 때 사용할 수 있는 것이 외적(Cross Product) 이다. 외적은 단순히 두 벡터가 평행한지를 확인하는 것에서 끝나지 않는다. 외적의 부호 를 이용하면 세 점이 어느 방향으로 놓여 있는지를 판단할 수 있고, 이것이 CCW(Counter Clockwise) 알고리즘이다. 그리고 CCW를 이용하면 두 선분이 서로 교차하는지도 판별할 수 있다. 이 글에서는 다음 흐름으로 정리해본다. 외적 → CCW → 선분 교차 판정 1. 외적 복습 2차원 벡터 $\overrightarrow{a}=(x_1,y_1)$, $\overrightarrow{b}=(x_2,y_2)$가 있을 때 외적은 다음과 같이 계산할 수 있다. $$ \overrightarrow{a}\times\overrightarrow{b} = x_1y_2-y_1x_2 $$ 외적의 크기는 두 벡터가 만드는 평행사변형의 넓이와 같다. $$ \left|\overrightarrow{a}\times\overrightarrow{b}\right| = \left|\overrightarrow{a}\right| \left|\overrightarrow{b}\right| \sin\theta $$ 두 벡터가 평행하다면 두 벡터 사이의 각도는 $0^\circ$ 또는 $180^\circ$이다. $$ \sin 0^\circ = 0 $$ $$ \sin 180^\circ = 0 $$ 따라서 평행한 두 벡터의 외적은 0이다. $$ \overrightarrow{a}\times\overrightarrow{b}=0 $$ 코드에서는 다음처럼 확인할 수 있다. long cross = x1 * y2 - y1 * x2; if (cross == 0) { // 두 벡터는 평행 } 기울기를 나눠서 비교하지 않기 때문에 수직선이나 실수 오차를 별도로 처리할 필요가 없다. 하지만 외적이 알려주는 정보는 단순히 0인가 아닌가 에서 끝나지 않는다. 2. 외적의 부호는 방향을 나타낸다 두 벡터의 외적 결과에는 부호가 있다. 외적 결과 의미 > 0 반시계 방향 < 0 시계 방향 = 0 일직선 즉 외적은 두 벡터가 평행한지를 판별할 뿐 아니라, 한 벡터에서 다른 벡터로 이동할 때 어느 방향으로 꺾이는지도 알려준다. 이 성질을 세 점에 적용한 것이 CCW 다. 3. CCW란? CCW는 Counter Clockwise 의 약자다. 세 점 A , B , C 가 있을 때 A → B → C 순서로 이동하면 진행 방향이 다음 중 무엇인지 판별한다. 반시계 방향 시계 방향 일직선 예를 들어 다음과 같은 경우를 생각해보자. C / A ── B A → B 로 이동한 뒤 C로 향하려면 반시계 방향으로 꺾어야 한다. 반대로 다음과 같다면 시계 방향이다. A ── B \ C 4. CCW는 어떻게 계산할까? 세 점을 다음과 같이 두자. $A=(A_x,A_y)$ $B=(B_x,B_y)$ $C=(C_x,C_y)$ A를 기준점으로 두 개의 벡터를 만든다. $$ \overrightarrow{AB} = (B_x-A_x,\ B_y-A_y) $$ $$ \overrightarrow{AC} = (C_x-A_x,\ C_y-A_y) $$ 그리고 두 벡터를 외적한다. $$ \overrightarrow{AB}\times\overrightarrow{AC} $$ 좌표로 풀어쓰면 다음과 같다. $$ (B_x-A_x)(C_y-A_y) - (B_y-A_y)(C_x-A_x) $$ 결과의 부호에 따라 방향을 판단할 수 있다. CCW(A, B, C) > 0 → 반시계 방향 CCW(A, B, C) < 0 → 시계 방향 CCW(A, B, C) = 0 → 일직선 Java로 구현하면 다음과 같다. static long ccw(Point a, Point b, Point c) { return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); } 방향만 필요하다면 외적의 실제 값 대신 -1 , 0 , 1 만 반환하도록 만들 수도 있다. static int ccw(Point a, Point b, Point c) { long cross = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); return Long.compare(cross, 0); } 반환값의 의미는 다음과 같다. 1 → 반시계 0 → 일직선 -1 → 시계 CCW는 별개의 복잡한 공식이라기보다 외적의 부호를 세 점의 방향 관계에 적용한 것 이라고 볼 수 있다. 5. CCW로 선분 교차 판정하기 이제 네 점 A , B , C , D 가 있고 두 선분 AB , CD 가 있다고 하자. 두 선분이 서로 가로질러 교차하려면 다음 두 조건을 모두 만족해야 한다. C와 D가 직선 AB의 서로 반대쪽에 있어야 한다. A와 B가 직선 CD의 서로 반대쪽에 있어야 한다. 6. C와 D가 AB의 서로 반대편에 있는지 확인 직선 AB를 기준으로 C와 D의 방향을 각각 확인한다. ccw(A, B, C); ccw(A, B, D); C와 D가 AB의 서로 반대편에 있다면 두 CCW 결과의 부호도 서로 다르다. 따라서 다음 조건을 만족한다. $$ CCW(A,B,C)\times CCW(A,B,D)<0 $$ 예를 들어 다음과 같은 형태다. C | A --+-- B | D 7. 한쪽만 검사하면 안 되는 이유 처음에는 다음 조건만 확인하면 될 것처럼 보인다. ccw(A, B, C) * ccw(A, B, D) < 0 하지만 이것만으로는 부족하다. C와 D를 잇는 선분이 실제 선분 AB가 아니라, AB를 무한히 연장한 직선 과 만날 수도 있기 때문이다. 따라서 반대 방향에서도 확인해야 한다. 즉 A와 B가 직선 CD의 서로 반대편에 있는지도 검사한다. $$ CCW(C,D,A)\times CCW(C,D,B)<0 $$ 결국 일반적인 교차 조건은 다음 두 조건을 모두 만족하는 것이다. $$ CCW(A,B,C)\times CCW(A,B,D)<0 $$ $$ CCW(C,D,A)\times CCW(C,D,B)<0 $$ 코드로 표현하면 다음과 같다. boolean intersect = ccw(a, b, c) * ccw(a, b, d) < 0 && ccw(c, d, a) * ccw(c, d, b) < 0; 핵심은 두 선분 각각을 기준으로 상대 선분의 두 끝점이 서로 반대편에 있는지 검사한다는 것 이다. 8. 선분의 끝점이 닿는 경우 두 선분이 정확히 한 끝점에서 만나는 경우도 교차로 취급한다고 하자. A ------ B | C 이 경우 하나 이상의 CCW 결과가 0이 된다. 따라서 < 0 이 아니라 <= 0 으로 조건을 확장해야 한다. $$ CCW(A,B,C)\times CCW(A,B,D)\leq0 $$ $$ CCW(C,D,A)\times CCW(C,D,B)\leq0 $$ 하지만 여기서 새로운 예외가 생긴다. 9. 네 점이 일직선 위에 있는 경우 다음 두 선분을 생각해보자. A ----- B C ----- D 두 선분은 서로 떨어져 있다. 하지만 네 점이 모두 같은 직선 위에 있기 때문에 모든 CCW 결과가 0이 된다. ccw(a, b, c) == 0; ccw(a, b, d) == 0; ccw(c, d, a) == 0; ccw(c, d, b) == 0; 따라서 단순히 다음 조건만 검사하면 ab <= 0 && cd <= 0 두 선분이 교차한다고 잘못 판단하게 된다. 즉 CCW는 두 선분이 같은 직선 위에 있다는 사실까지만 알려줄 뿐, 실제 구간이 겹치는지는 알려주지 않는다. 그래서 두 선분이 모두 일직선 위에 있는 경우에는 실제 구간이 겹치는지 추가로 확인해야 한다. 10. 일직선 선분의 겹침 판정 두 선분을 좌표 순서대로 정렬했다고 생각해보자. A -------- B C -------- D 두 선분이 겹치려면 다음 조건을 만족해야 한다. A <= D C <= B 반대로 A ----- B C ----- D 처럼 B < C 라면 두 선분은 떨어져 있다. 2차원 좌표에서는 점을 (x, y) 의 사전식 순서로 비교할 수 있다. static int compare(Point a, Point b) { if (a.x != b.x) { return Long.compare(a.x, b.x); } return Long.compare(a.y, b.y); } 따라서 선분 교차 판정은 크게 두 단계로 생각할 수 있다. 1. CCW를 이용해 두 선분의 방향 관계를 확인한다. 2. 네 점이 모두 일직선이라면 실제 선분 구간이 겹치는지 추가로 확인한다. 11. 전체 Java 구현 class Point { long x; long y; Point(long x, long y) { this.x = x; this.y = y; } } public class Geometry { static int ccw(Point a, Point b, Point c) { long cross = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); return Long.compare(cross, 0); } static boolean intersects(Point a, Point b, Point c, Point d) { int abC = ccw(a, b, c); int abD = ccw(a, b, d); int cdA = ccw(c, d, a); int cdB = ccw(c, d, b); int ab = abC * abD; int cd = cdA * cdB; // 네 점이 모두 일직선 위에 있는 경우 if (ab == 0 && cd == 0) { if (compare(a, b) > 0) { Point temp = a; a = b; b = temp; } if (compare(c, d) > 0) { Point temp = c; c = d; d = temp; } return compare(a, d) <= 0 && compare(c, b) <= 0; } return ab <= 0 && cd <= 0; } static int compare(Point a, Point b) { if (a.x != b.x) { return Long.compare(a.x, b.x); } return Long.compare(a.y, b.y); } } 12. 왜 long 을 사용하는가? CCW 계산에서는 다음과 같은 곱셈이 발생한다. (b.x - a.x) * (c.y - a.y) 각 좌표가 int 범위 안에 있다고 해서 곱셈 결과도 int 범위 안에 있는 것은 아니다. 예를 들어 값이 100,000 정도라면 100,000 × 100,000 = 10,000,000,000 이 되어 이미 int 의 최대값을 넘어간다. 따라서 좌표 범위가 크다면 CCW 계산은 long 으로 처리하는 것이 안전하다. 또 위 구현처럼 ccw() 에서 실제 외적 값을 그대로 반환하지 않고 -1 , 0 , 1 로 정규화해 반환하면 이후 ccw(a, b, c) * ccw(a, b, d) 계산에서도 오버플로를 걱정할 필요가 없다. 13. 정리 처음에는 외적을 단순히 평행 여부를 판별하는 공식으로 생각할 수 있다. 외적 == 0 → 두 벡터가 평행 하지만 외적의 부호 까지 보면 더 많은 정보를 얻을 수 있다. 외적 > 0 → 반시계 방향 외적 < 0 → 시계 방향 외적 == 0 → 일직선 이 성질을 세 점에 적용한 것이 CCW 다. 그리고 CCW를 두 선분 각각의 관점에서 적용하면 선분 교차 여부를 판별할 수 있다. 외적 ↓ 방향 판별 ↓ CCW ↓ 선분 교차 판정 CCW와 선분 교차 판정을 각각 독립된 공식으로 외우기보다, 결국 모든 것이 다음 외적 계산에서 출발한다는 점을 이해하는 것이 중요하다. $$ (x_1,y_1)\times(x_2,y_2) = x_1y_2-y_1x_2 $$ 외적의 0 여부와 부호가 무엇을 의미하는지 이해하면 CCW와 선분 교차 판정까지 자연스럽게 연결된다. 특히 코딩테스트에서는 기울기를 직접 계산하는 것보다 외적을 사용하면 다음과 같은 장점이 있다. 나눗셈이 필요 없다. 수직선을 별도로 처리하지 않아도 된다. 실수 오차를 피할 수 있다. 좌표 문제에서 방향이나 교차 여부를 판단해야 한다면 기울기보다 외적과 CCW를 먼저 떠올려볼 수 있다.
토큰화와 임베딩을 따로 공부할 때는 각각의 역할만 봤는데, 이번 장에서는 둘이 LSTM과 연결된다. 리뷰 문장 하나가 들어와서 긍정 또는 부정이라는 결과로 나오는 흐름을 따라가 봤다. 이 글은 교재 코드를 읽고 정리한 리뷰다. 전체 데이터를 내려받아 학습을 실행한 실험 기록은 아니다. 전체 흐름부터 잡기 교재는 네이버 영화 리뷰의 긍정·부정 분류를 다룬다. 긍정은 1, 부정은 0이다. 중복과 결측치를 정리하고, 한글 중심으로 정제한 뒤 Mecab 토큰화와 불용어 제거를 수행한다. 이후 학습 데이터에서 단어 사전을 만들고 정수 인코딩한다. 이 장에서는 <PAD> 가 0, <UNK> 가 1이다. 앞서 본 예제와 번호가 다르므로 그대로 가정하면 안 되겠다. 리뷰는 길이 30으로 패딩하거나 자르고, Embedding → LSTM → Linear를 거쳐 두 클래스의 점수를 얻는다. 검증 손실이 가장 낮은 가중치를 저장한 뒤 테스트한다. 교재에 제시된 테스트 정확도는 84.92%다. WikiDocs 영화 리뷰 분류 챕터 이번에는 모델 이름을 외우는 것보다 각 단계에서 데이터가 어떤 모습인지 적어 보는 쪽이 더 도움이 됐다. 텐서 크기로 연결해 보기 배치 4개, 길이 7, 임베딩 크기 6, 은닉 크기 8인 작은 예를 따로 생각해 봤다. 패딩이 없는 동일 길이 입력의 구조 확인용 코드이며, 학습 성능을 측정하는 예제는 아니다. import torch import torch.nn as nn x = torch.tensor([ [2, 3, 4, 5, 6, 7, 8], [3, 4, 5, 6, 7, 8, 9], [4, 5, 6, 7, 8, 9, 2], [5, 6, 7, 8, 9, 2, 3], ], dtype=torch.long) embedding = nn.Embedding(10, 6) lstm = nn.LSTM(6, 8, batch_first=True) classifier = nn.Linear(8, 2) embedded = embedding(x) sequence_output, (h_n, c_n) = lstm(embedded) logits = classifier(h_n[0]) # 예상되는 크기 print(embedded.shape) # torch.Size([4, 7, 6]) print(sequence_output.shape) # torch.Size([4, 7, 8]) print(h_n.shape) # torch.Size([1, 4, 8]) print(logits.shape) # torch.Size([4, 2]) Embedding에서는 ID마다 벡터가 붙는다. LSTM의 sequence_output 에는 모든 시점의 은닉 상태가 있고, h_n 에는 마지막 은닉 상태가 있다. 위 코드는 단방향·1층이므로 h_n[0] 의 크기는 (4, 8) 이다. 이를 Linear에 넣으면 리뷰마다 두 점수를 얻는다. PyTorch Embedding 문서 , PyTorch LSTM 문서 여기서 “LSTM을 지나면 무조건 2차원이 된다”라고 기억하면 틀린다. 모든 시점의 출력과 마지막 상태를 구분해야 한다. 점수와 확률을 구분하기 logits 는 아직 확률로 정규화하지 않은 점수다. CrossEntropyLoss 에는 softmax를 미리 적용하지 않고 이 점수를 그대로 넣는다. 클래스 ID를 정답으로 사용하는 경우 정답은 배치 크기만큼의 정수 텐서다. labels = torch.tensor([1, 0, 1, 0], dtype=torch.long) loss = nn.CrossEntropyLoss()(logits, labels) predicted = logits.argmax(dim=1) dim=1 은 각 리뷰의 클래스 점수 두 개 중 큰 값의 위치를 찾는다는 뜻이다. (4, 2) 에서 리뷰별 예측 ID 네 개를 얻는다. 확률이 필요할 때는 별도로 softmax를 사용할 수 있지만, 손실 계산의 입력과는 구분해 둔다. PyTorch CrossEntropyLoss 문서 코드에서 더 확인하고 싶은 부분 교재는 뒤쪽에 패딩을 붙이고 최종 은닉 상태를 사용한다. 그런데 모델의 Embedding에는 padding_idx 가 없고, 예측 함수는 학습 때와 달리 동일한 정제·길이 맞춤을 하지 않는다. 이 차이는 직접 재현할 때 점검하고 싶다. 교재 코드 특히 padding_idx 를 지정하는 것과 LSTM이 패딩 시점을 건너뛰는 것은 별개다. 실제 길이를 전달하는 pack_padded_sequence 를 사용하면 가변 길이 시퀀스를 PackedSequence로 만들 수 있다. LSTM은 이런 입력도 받는다. 내 다음 실습에서는 패딩을 처리한 마지막 상태와 실제 문장 끝의 상태를 구분해 비교해 보려고 한다. PyTorch 패킹 문서 , LSTM 문서 다음 실습에서 남길 기록 이번 장을 읽고 나니 정확도 숫자 하나보다 확인할 항목이 먼저 보였다. 직접 실행할 때는 전처리 전후의 샘플, 사전에 없는 토큰의 비율, 잘리는 리뷰, 오분류 문장을 함께 기록하려고 한다. 학습이 돌아간다는 것과 내가 의도한 입력을 모델에 넣었다는 것은 별도로 확인해야겠다. 다음에는 긴 코드를 한 번에 따라 치기보다, 리뷰 하나가 토큰과 ID를 거쳐 점수로 바뀌는 과정을 먼저 출력해 보고 싶다.
Construction sites often involve more than simply moving materials from one point to another. Soil, aggregate, debris, concrete components, and other supplies may need to be lifted and placed at specific distances or heights. In these situations, the reach of loading equipment becomes an important consideration. A machine with suitable reach can position material without constant repositioning, which may be especially useful when working areas are crowded or loading points are difficult to approach directly. However, reach should always be considered alongside capacity, stability, site dimensions, and the type of material being handled. Understanding the Importance of Reach Reach describes how far a machine can extend its working equipment from its operating position. In practical construction work, this can affect where material can be placed and how frequently the machine needs to move. Consider a site where excavated material must be transferred into trucks positioned beside an excavation area. If the loading machine cannot reach the truck comfortably, it may need to reposition repeatedly. That extra movement can consume working time and make the site layout more complicated. Greater reach can therefore provide an advantage when the distance between the machine and the loading point is significant. The exact requirement depends on the site's layout and the task being performed. Situations Where Extended Reach Helps Several construction activities can benefit from equipment that provides greater working reach. Material may need to be loaded into trucks, transferred over barriers, placed into stockpiles, or moved around obstacles. On large development sites, the loading area may also be separated from the material source by temporary structures or other site activities. Reach can become particularly useful when there is limited room for repositioning. For example, if several machines are operating in the same area, repeatedly moving a loader may create unnecessary congestion. Equipment capable of reaching the required point from a stable position can make material handling more straightforward. However, extended reach should be selected because the project needs it, not simply because a machine offers a larger working range. Capacity Still Matters Reach alone does not determine whether a loader is suitable. As a load moves farther from the machine, operating conditions can change. The equipment must remain appropriate for the weight and type of material being handled at the required position. This means the maximum rated capacity should be examined alongside the expected load and working distance. Bucket size is another consideration. A larger bucket may move more material during each cycle, but its suitability depends on the machine's capacity and the density of the material. The goal is to find a combination of reach and capacity that corresponds with the actual operation. Examine the Site Layout Before arranging equipment, look carefully at where materials will be collected, loaded, and deposited. Identify the excavation zone, stockpile area, truck position, access routes, and other active work areas. This gives a clearer picture of how far the machine actually needs to reach. A site with plenty of open space may allow frequent repositioning without major inconvenience. A restricted site may benefit more from equipment that can cover a larger working area from one position. Temporary structures and stored materials should also be included in the assessment. Good site planning can sometimes reduce the amount of reach required simply by improving the positioning of materials and vehicles. Selecting Equipment for Abu Dhabi Conditions Construction projects in Abu Dhabi can differ considerably in scale, layout, and working environment. Equipment should therefore be selected according to the conditions of the specific project. For businesses arranging Boom Loader Rental in Abu Dhabi , the useful question is how the machine's working range corresponds with the planned loading operation. Consider the required reach, load weight, bucket or attachment configuration, ground conditions, available maneuvering space, and expected rental duration. These factors provide a clearer basis for selecting equipment than relying on a single specification. Transportation should also be discussed before booking, particularly when larger machinery needs to be delivered to a busy or restricted site. Think About Material Characteristics Not all loads behave in the same way. Loose soil, gravel, aggregate, debris, and packaged materials can have different weights and handling requirements. The machine configuration should reflect the material being moved. The size and shape of the material can influence bucket selection as well. Certain loads may require specialized attachments rather than a standard bucket. Before equipment arrives, identify the materials that will be handled most frequently and communicate this information to the rental provider. This can help ensure that the selected machine is equipped for the intended work. Consider the Working Height Reach is not limited to horizontal distance. The height at which materials need to be positioned can also influence equipment selection. Loading material into a high-sided truck, reaching an elevated stockpile, or placing material at a raised location may require a different working configuration from ground-level loading. Review both the required height and distance together. The machine should be capable of reaching the intended position while maintaining appropriate operating capacity. Looking only at maximum horizontal reach can result in an incomplete assessment. Coordinate With Trucks and Other Machinery Loading equipment rarely works independently. If an excavator supplies material to a loader, and the loader transfers that material into trucks, the timing of all three activities can affect productivity. Truck positioning is particularly important. Vehicles should be placed where the loading machine can reach them efficiently without interfering with other site operations. If the loader must repeatedly wait for trucks to arrive, its potential capacity is not being fully utilized. A coordinated loading area can reduce unnecessary movement and keep material flowing through the site more consistently. Assess Ground Conditions The surface on which loading machinery operates should provide suitable support for the planned activity. Uneven ground, loose soil, slopes, or recently disturbed surfaces can affect machine movement and positioning. Before selecting equipment, determine whether the work will take place on prepared ground, paved surfaces, compacted soil, or another type of terrain. If the working area needs preparation before machinery arrives, include that activity in the project schedule. Stable positioning is particularly important when the machine is handling substantial loads at extended reach. Look at Attachments and Configuration A loader's usefulness can depend heavily on how it is configured. Bucket dimensions, attachment type, hydraulic capabilities, and other machine characteristics should correspond with the material and operation. A standard configuration may be sufficient for moving loose soil, while another task could require a different attachment. Discuss the full scope of work before finalizing the rental. This allows the equipment provider to identify a configuration that supports the required activities. Avoid selecting attachments simply because they are available. Each component should have a practical purpose within the project. Estimate the Rental Period The expected duration of the loading operation should be established before equipment is booked. Short-term material movement may require only a few working days, while major earthmoving projects could need loading machinery for considerably longer. Consider the construction sequence and identify when the machine will actually be productive. If loading is delayed because excavation is incomplete or trucks are unavailable, the rental period may extend without equivalent productivity. A realistic schedule helps align machinery costs with actual site usage. Keep Transportation in Mind Equipment with greater capacity or reach may require more careful transportation planning. Confirm the machine's delivery requirements, unloading location, and access route before the rental begins. The site entrance should provide sufficient space for the transport vehicle and machinery. Once unloaded, there should also be a clear route to the working area. Planning these details ahead of time reduces the possibility of delivery delays. Collection arrangements should be considered as well, particularly when the equipment is needed only during a specific phase of the project. Use Reach Where It Provides Practical Value Greater loading reach can make certain construction operations more convenient by reducing unnecessary repositioning and allowing material to be placed farther from the machine. Its value, however, depends on the complete working situation. Load capacity, material characteristics, height, ground conditions, attachments, site layout, and machine positioning all contribute to effective performance. Before selecting equipment, map out the actual movement of materials and determine where the machine needs to work from. This makes it easier to identify the required reach rather than choosing a machine based on a specification alone. When equipment is matched closely with the loading environment, extended reach can become a practical part of a well-organized construction process, supporting smoother material movement while reducing avoidable repositioning around the site.